Created
June 23, 2012 13:56
-
-
Save shinaisan/2978367 to your computer and use it in GitHub Desktop.
Bezier Worm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* -*- 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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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