Skip to content

Instantly share code, notes, and snippets.

@shinaisan
Created June 23, 2012 13:56
Show Gist options
  • Save shinaisan/2978367 to your computer and use it in GitHub Desktop.
Save shinaisan/2978367 to your computer and use it in GitHub Desktop.
Bezier Worm
/* -*- mode: java; indent-tabs-mode; nil -*- */
/*
* Inspired by the great book "Processing: Creative Coding and Computational Art" by Ira Greenberg.
*/
int canvasWidth = 500;
int canvasHeight = 500;
int graphWidth = canvasWidth - 40;
int graphHeight = 200;
BezierTuner bezierTuner;
BezierWorm bezierWorm;
DraggableCircle[] draggables;
Point wormFood;
Point wormFoodVel;
BrokenLineFunc draggableAlpha;
void setup() {
frameRate(16);
size(canvasWidth, canvasHeight);
// smooth();
Point[] points = new Point[4];
draggables = new DraggableCircle[4];
for (int i = 0; i < points.length; i++) {
points[i] = new Point(0, 0);
}
int graphLeft = (canvasWidth - graphWidth) / 2;
int graphBottom = canvasHeight - graphLeft;
int graphTop = graphBottom - graphHeight;
int r = 32;
draggables[0] = new DraggableCircle(graphLeft, graphBottom, r);
draggables[1] = new DraggableCircle(graphLeft + graphWidth / 4, graphBottom - graphHeight / 4, r);
draggables[2] = new DraggableCircle(graphLeft + graphWidth / 2, graphBottom - graphHeight / 2, r);
draggables[3] = new DraggableCircle(graphLeft + graphWidth * 3 / 4, graphBottom, r);
for (int i = 0; i < draggables.length; i++) {
draggables[i].setRange(draggables[i].x, graphTop, draggables[i].x, graphBottom);
}
draggableAlpha = new BrokenLineFunc({0, 1, 2}, {0, 255, 0});
draggableAlpha.repeat = true;
bezierTuner = new BezierTuner(graphLeft, graphTop, graphWidth, graphHeight, points);
bezierWorm = new BezierWorm(50, 50, 400, 100, 100, bezierTuner);
wormFood = new Point(20, 200);
wormFoodVel = new Point(1, 1);
bezierWorm.feed(wormFood);
}
void draw() {
update();
background(0);
int currentTimeSec = millis() / 1000;
strokeWeight(3);
stroke(255);
wormFood.draw();
bezierWorm.draw();
bezierTuner.draw();
strokeWeight(3);
stroke(255, draggableAlpha.calc(currentTimeSec));
for (int i = 0; i < draggables.length; i++) {
draggables[i].draw();
}
}
void update() {
int mx = mouseX;
int my = mouseY;
for (int i = 0; i < draggables.length; i++) {
draggables[i].track(mx, my);
}
for (int i = 0; i < bezierTuner.points.length; i++) {
bezierTuner.points[i].moveTo(draggables[i].x, draggables[i].y);
}
bezierWorm.move();
moveWormFood();
}
void moveWormFood() {
wormFood.add(wormFoodVel);
if (wormFood.x < 0 || wormFood.x >= canvasWidth) {
wormFoodVel.x *= -1;
}
if (wormFood.y < 0 || wormFood.y >= canvasHeight - graphHeight) {
wormFoodVel.y *= -1;
}
}
void mousePressed() {
for (int i = 0; i < draggables.length; i++) {
draggables[i].mousePressed();
}
}
void mouseReleased() {
for (int i = 0; i < draggables.length; i++) {
draggables[i].mouseReleased();
}
}
interface Drawable {
void draw();
}
class Point implements Drawable {
int x;
int y;
Point(int x, int y) {
moveTo(x, y);
}
void moveTo(int x, int y) {
this.x = x;
this.y = y;
}
void add(int dx, int dy) {
this.x += dx;
this.y += dy;
}
void add(Point pt) {
add(pt.x, pt.y);
}
void scalar(float f) {
this.x *= f;
this.y *= f;
}
void draw() {
point(this.x, this.y);
}
}
class DraggableCircle implements Drawable {
int left;
int top;
int right;
int bottom;
int x;
int y;
int radius;
boolean dragging;
DraggableCircle(int x, int y, int r) {
setRange(x, y, x, y);
moveTo(x, y);
this.radius = r;
this.dragging = false;
}
void setRange(int left, int top, int right, int bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
moveTo(this.x, this.y);
}
boolean isBusy() {
return (this.isMouseOver() || this.dragging);
}
boolean isMouseOver() {
int mx = mouseX;
int my = mouseY;
int dx = this.x - mx;
int dy = this.y - my;
return (dx * dx + dy * dy <= this.radius * this.radius);
}
void mousePressed() {
this.dragging = isMouseOver();
}
void mouseReleased() {
this.dragging = false;
}
void moveTo(int x, int y) {
if (x < this.left) {
x = this.left;
} else if (x >= this.right) {
x = this.right;
}
if (y < this.top) {
y = this.top;
} else if (y >= this.bottom) {
y = this.bottom;
}
this.x = x;
this.y = y;
}
void track(int mx, int my) {
if (this.dragging) {
moveTo(mx, my);
}
}
void draw() {
if (this.isBusy()) {
ellipse(this.x, this.y, this.radius, this.radius);
}
}
}
class BrokenLineFunc {
int[] xs;
int[] ys;
int invalidValue;
boolean repeat;
int maxX;
BrokenLineFunc(int[] xs, int [] ys) {
this.maxX = 1;
if ((xs.length != ys.length) || (xs.length <= 1)) {
xs = null;
ys = null;
}
for (int i = 1; i < xs.length; i++) {
if (xs[i] < xs[i - 1]) {
xs = null;
ys = null;
break;
}
}
this.xs = xs;
this.ys = ys;
this.maxX = xs[xs.length - 1];
this.invalidValue = 0;
this.repeat = false;
}
int calc(int x) {
if (this.xs == null || this.ys == null || this.maxX == 0) {
return (this.invalidValue);
}
if (this.repeat) {
x %= this.maxX;
}
for (int i = 0; i < this.xs.length - 1; i++) {
if (this.xs[i] <= x && x < this.xs[i + 1]) {
float slope = (this.ys[i + 1] - this.ys[i]) / (this.xs[i + 1] - this.xs[i]);
return (this.ys[i] + round(slope * (x - this.xs[i])));
}
}
return (this.invalidValue);
}
}
int combination(int n, int k) {
if (n <= 0 || k <= 0) {
return (1);
}
if (k > n / 2) {
return (combination(n, n - k));
}
if (k == 1) {
return (n);
}
return (combination(n, k - 1) + combination(n - 1, k));
}
// We can just use bezierPoint() without bothering with this class?
class BezierFunc {
Point[] points;
int invalidValue;
BezierFunc(Point[] points) {
this.points = points;
this.invalidValue = 0;
}
Point calc(float t) {
if (this.points == null) {
return (this.invalidValue);
}
int n = this.points.length;
float x = 0.0;
float y = 0.0;
for (int i = 0; i < n; i++) {
float coeff = pow(t, i) * pow(1 - t, n - 1 - i);
coeff *= combination(n - 1, i);
x += coeff * this.points[i].x;
y += coeff * this.points[i].y;
}
return (new Point(round(x), round(y)));
}
}
class BezierTuner implements Drawable {
int left;
int top;
int width;
int height;
Point[] points;
BezierFunc bezierFunc;
BezierTuner(int left, int top, int width, int height, Point[] points) {
this.left = left;
this.top = top;
this.width = width;
this.height = height;
if (points.length != 4) {
points = new Point[4];
for (int i = 0; i < points.length; i++) {
points[i].x = 0;
points[i].y = 0;
}
}
this.points = points;
this.bezierFunc = new BezierFunc(this.points);
}
int calc(float t) { // 0 <= t <= 1
Point p = this.bezierFunc.calc(t);
return (this.top + this.height - p.y);
}
void draw() {
int bottom = this.top + this.height;
noFill();
strokeWeight(1);
stroke(255, 255);
line(this.left, this.top, this.left, bottom);
line(this.left, bottom, this.left + this.width, bottom);
strokeWeight(1);
stroke(255, 255);
bezier(this.points[0].x, this.points[0].y,
this.points[1].x, this.points[1].y,
this.points[2].x, this.points[2].y,
this.points[3].x, this.points[3].y);
strokeWeight(8);
stroke(#00ffff);
for (int i = 0; i < points.length; i++) {
points[i].draw();
}
}
}
class BezierWorm implements Drawable {
int moveCount;
Point[] segments;
Point[] vels;
Point food;
BezierTuner bezierTuner;
BezierWorm(int startX, int startY, int stopX, int stopY, int segmentCount, BezierTuner bezierTuner) {
int dx = (stopX - startX);
int dy = (stopY - startY);
this.segments = new Point[segmentCount];
this.vels = new Point[segmentCount];
for (int i = 0; i < segmentCount; i++) {
this.segments[i] = new Point(startX + i * dx / segmentCount,
startY + i * dy / segmentCount);
this.vels[i] = new Point(0, 0);
}
this.bezierTuner = bezierTuner;
}
void feed(Point food) {
this.food = food;
}
void move() {
moveCount++;
int n = this.segments.length;
if (this.food == null) {
return;
}
int prevX = this.food.x;
int prevY = this.food.y;
float factor = 0.01;
for (int i = 0; i < n; i++) {
vels[i].scalar(factor);
int dx = factor * (prevX - this.segments[i].x);
int dy = factor * (prevY - this.segments[i].y);
this.vels[i].add(dx, dy);
this.segments[i].add(this.vels[i]);
prevX = this.segments[i].x;
prevY = this.segments[i].y;
factor = 0.2 + 0.2 * sin(radians(16*i + 16*moveCount));
}
}
void draw() {
strokeWeight(3);
stroke(255, 128);
fill(0);
for (int i = 0; i < this.segments.length; i++) {
float t = i / this.segments.length;
int r = this.bezierTuner.calc(t);
ellipse(this.segments[i].x, this.segments[i].y, r, r);
}
}
}
<html>
<head>
<title>Bezier Worm</title>
<script type="text/javascript" src="processing.js"></script>
</head>
<body>
<h1>Bezier Worm</h1>
<br/>
<script type="application/processing" src="bw.processing"></script><canvas></canvas>
<br/>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment