A circle of points connected by springs. They are constrained to a slowly growing circle. When they get too far from their neighbors, they split. Each point leaves a line behind as it moves.
Log in to post a comment.
const RAD_INITIAL = 10; const RAD_FINAL = 90; const NUM_SEEDS = 21; // min=3, max=100, step=1 const NUM_STEPS = 100; // min=5, max=500, step=1 const SEED_JITTER = 0.9; // min=0, max=1, step=0.05 const LERP_FRACTION = 0.1; // min=0, max=1.-, step=0.05 const SPLIT_DIST = 15; // min=2, max=40, step=1 Canvas.setpenopacity(1); const turtle = new Turtle(); turtle.pendown(); let rand = (min, max) => Math.random() * (max - min) + min; let randInt = (min, max) => Math.floor(rand(min, max)); let remap = (x, oldmin, oldmax, newmin, newmax) => { let t = (x - oldmin) / (oldmax - oldmin); return t * (newmax - newmin) + newmin; } let Point = (x, y) => { let self = {x: x, y: y}; self.len = () => Math.sqrt((self.x * self.x) + (self.y * self.y)); self.mul = (f) => { self.x *= f; self.y *= f; } self.setlen = (newlen) => { self.mul(newlen/self.len()); } self.saveold = () => { self.xold = self.x; self.yold = self.y; } self.lerpto = (p2, t) => { self.x = (self.x * (1-t)) + p2.x * t; self.y = (self.y * (1-t)) + p2.y * t; } self.clone = () => { let p2 = Point(self.x, self.y); p2.xold = self.xold; p2.yold = self.yold; return p2; } self.jitter = (r) => { self.x += rand(-r, r); self.y += rand(-r, r); } return self; }; let randPoint = () => Point(rand(-1, 1), rand(-1, 1)); let dist = (p1, p2) => { let dx = p1.x - p2.x; let dy = p1.y - p2.y; return Math.sqrt(dx*dx + dy*dy); } let distold = (p1, p2) => { let dx = p1.xold - p2.xold; let dy = p1.yold - p2.yold; return Math.sqrt(dx*dx + dy*dy); } let points = []; // The walk function will be called until it returns false. console.log('-------------------'); function walk(ii) { if (ii === NUM_STEPS) { return false; } let rad0 = remap(ii, 0, NUM_STEPS, RAD_INITIAL, RAD_FINAL); let rad1 = remap(ii+1, 0, NUM_STEPS, RAD_INITIAL, RAD_FINAL); // set out initial points if (ii === 0) { for (let pp = 0; pp < NUM_SEEDS; pp++) { let theta = remap(pp + Math.random() * SEED_JITTER, 0, NUM_SEEDS, 0, Math.PI * 2); let p = Point(Math.sin(theta), Math.cos(theta)); p.setlen(rad0); points.push(p); } } // save existing positions for (let p of points) { p.saveold(); } // smooth with neighbors and normalize to new radius for (let pp = 0; pp < points.length; pp++) { let p0 = points[(pp + 0) % points.length]; let p1 = points[(pp + 1) % points.length]; let p2 = points[(pp + 2) % points.length]; let midpoint = Point((p0.xold + p2.xold)/2, (p0.yold + p2.yold)/2); p1.lerpto(midpoint, LERP_FRACTION); p1.setlen(rad1); } // split when dist between neighbors is large enough let points2 = [...points]; for (let pp = points2.length-1; pp >= 0; pp--) { let p0 = points2[(pp + 0) % points2.length]; let p1 = points2[(pp + 1) % points2.length]; let p2 = points2[(pp + 2) % points2.length]; if (dist(p0, p2) > SPLIT_DIST) { let p1b = p1.clone(); p1b.jitter(0.1); p1b.setlen(rad1); points.splice(pp + 1, 0, p1b); } } // draw for (let p of points) { turtle.jump(p.xold, p.yold); turtle.goto(p.x, p.y); } return true; }