A (rough) go at the algorithm suggested by @jeremydouglass above:
float margin = 50.0;
PMatrix3D crb = new PMatrix3D();
float curveTightness = 0.01;
int count = 20;
int draw = 250;
float animRate = 0.005;
PVector[] points;
PVector[] anim;
boolean closedLoop = false;
void setup() {
size(600, 600, P2D);
strokeCap(ROUND);
strokeJoin(ROUND);
curveTightness(curveTightness);
catmullBasis(curveTightness, crb);
points = new PVector[count];
for (int i = 0; i < count; ++i) {
points[i] = new PVector();
}
anim = new PVector[draw];
for (int i = 0; i < draw; ++i) {
anim[i] = new PVector();
}
randomizePoints();
}
void mouseReleased() {
if (mouseButton == LEFT) {
randomizePoints();
}
if (mouseButton == RIGHT) {
closedLoop = !closedLoop;
}
}
void draw() {
surface.setTitle(nfs(frameRate, 1, 1));
float t = (frameCount * animRate);
t -= floor(t);
background(#fff7d5);
noFill();
stroke(#202020);
strokeWeight(3.0);
beginShape();
for (PVector p : points) {
curveVertex(p.x, p.y);
}
if (closedLoop) {
curveVertex(points[0].x, points[0].y);
curveVertex(points[1].x, points[1].y);
curveVertex(points[2].x, points[2].y);
endShape(CLOSE);
} else {
endShape();
}
strokeWeight(7.5);
stroke(#007fff);
PVector prev = points[1];
for (int i = 0; i < draw; ++i) {
float prc = i / (draw - 1.0);
float sclprc = t * prc;
PVector a = anim[i];
curvePoints(crb, closedLoop, points, sclprc, a);
line(prev.x, prev.y, a.x, a.y);
prev = a;
}
}
void randomizePoints() {
for (PVector p : points) {
p.set(
random(margin, width - margin),
random(margin, height - margin));
}
}
PVector curvePoints(
PMatrix3D cb,
boolean closedLoop,
PVector[] pts,
float t,
PVector target) {
int len = pts.length;
float tScaled = 0.0;
int i = 0;
PVector a;
PVector b;
PVector c;
PVector d;
if (closedLoop) {
tScaled = (t - floor(t)) * len;
i = (int) tScaled;
a = pts[mod(i, len)];
b = pts[mod(i + 1, len)];
c = pts[mod(i + 2, len)];
d = pts[mod(i + 3, len)];
} else {
if (t <= 0.0) {
return target.set(pts[1]);
}
if (t >= 1.0) {
return target.set(pts[len - 2]);
}
tScaled = t * (len - 3);
i = (int) tScaled;
a = pts[i];
b = pts[i + 1];
c = pts[i + 2];
d = pts[i + 3];
}
return curvePoint(cb, a, b, c, d, tScaled - i, target);
}
PVector curvePoint(
PMatrix3D cb,
PVector a,
PVector b,
PVector c,
PVector d,
float t,
PVector target) {
if ( target == null ) {
target = new PVector();
}
float tt = t * t;
float ttt = tt * t;
float acoeff = ttt * cb.m00 + tt * cb.m10 + t * cb.m20 + cb.m30;
float bcoeff = ttt * cb.m01 + tt * cb.m11 + t * cb.m21 + cb.m31;
float ccoeff = ttt * cb.m02 + tt * cb.m12 + t * cb.m22 + cb.m32;
float dcoeff = ttt * cb.m03 + tt * cb.m13 + t * cb.m23 + cb.m33;
return target.set(
a.x * acoeff + b.x * bcoeff + c.x * ccoeff + d.x * dcoeff,
a.y * acoeff + b.y * bcoeff + c.y * ccoeff + d.y * dcoeff,
a.z * acoeff + b.z * bcoeff + c.z * ccoeff + d.z * dcoeff);
}
public PMatrix3D catmullBasis(float s, PMatrix3D target) {
if ( target == null ) {
target = new PMatrix3D();
}
float u = 1.0 - s;
float th = (s - 1.0) * 0.5;
float uh = u * 0.5;
float v = (s + 3.0) * 0.5;
target.set(
th, v, -v, uh,
u, ( -5.0 - s ) * 0.5, s + 2.0, th,
th, 0.0, uh, 0.0,
0.0, 1.0, 0.0, 0.0);
return target;
}
int mod(int a, int b) {
int result = a - b * ( a / b );
return result < 0 ? result + b : result;
}
I found it helpful to look at curveInit and curvePoint to see how they worked. One of the ambiguities I ran into was how to handle a closed loop.
If you’re approximating a curve with line segments, you’d probably want to refine so that more line segments are alotted to covering a long curve as the percentage increases; and fewer to a shorter curve or minimal percentage.
I got some jittering with JAVA2D
vs. P2D
.
Best,
Jeremy