Based off of: github.com/jasonwebb…nization-experiments
Log in to post a comment.
// --- Sliders ---
const attractorCount = 1200; // min=50 max=3000 step=50 "Food" points (density)
const influenceRadius = 10; // min=4 max=40 step=1 Influence distance
const killDistance = 3; // min=1 max=15 step=0.5 Attractor consumed below this
const branchLength = 2; // min=0.5 max=10 step=0.5 Growth step length
const smoothPasses = 2; // min=0 max=4 step=1 Chaikin smoothing iterations
const randomSeed = 1; // min=0 max=10 step=1 Set to change pattern
////////////////////////////////////////////////////////////////
Canvas.setpenopacity(1);
// PRNG – mulberry32 (repeatable)
function mulberry32(a){return function(){let t=a+=0x6D2B79F5;t=Math.imul(t^t>>>15,t|1);t^=t+Math.imul(t^t>>>7,t|61);return((t^t>>>14)>>>0)/4294967296;}};
const rnd = mulberry32(randomSeed);
function Node(x,y,parent){this.x=x;this.y=y;this.parent=parent;this.gx=0;this.gy=0;this.count=0;this.children=[];}
const nodes=[new Node(0,-90,-1)]; // root near bottom‑centre
const attractors=[];
// --- Make attractors in a disk of radius 95 ---
for(let i=0;i<attractorCount;i++){
const a=rnd()*Math.PI*2;
const r=Math.sqrt(rnd())*95;
attractors.push({x:Math.cos(a)*r,y:Math.sin(a)*r});
}
// --- Growth phase (culled version to avoid overwork) ---
let iterations=0;
while(attractors.length && iterations<1800){ // cap iterations for perf
// Reset growth vectors
for(const n of nodes){n.gx=0;n.gy=0;n.count=0;}
// Influence calculations
for(let ai=attractors.length-1;ai>=0;ai--){
const att=attractors[ai];
let closest=-1,minD2=Infinity;
for(let ni=0;ni<nodes.length;ni++){
const n=nodes[ni];
const dx=att.x-n.x,dy=att.y-n.y;
const d2=dx*dx+dy*dy;
if(d2<minD2){minD2=d2;closest=ni;}
}
const d=Math.sqrt(minD2);
if(d<killDistance){
attractors.splice(ai,1);
} else if(d<influenceRadius){
const n=nodes[closest];
n.gx+=(att.x-n.x)/d;
n.gy+=(att.y-n.y)/d;
n.count++;
}
}
// Spawn new nodes
const newNodes=[];
for(let i=0;i<nodes.length;i++){
const n=nodes[i];
if(n.count>0){
let dx=n.gx/n.count,dy=n.gy/n.count;
const mag=Math.hypot(dx,dy);
dx=dx/mag*branchLength;
dy=dy/mag*branchLength;
const nx=n.x+dx,ny=n.y+dy;
let ok=true;
for(const o of nodes){if((nx-o.x)**2+(ny-o.y)**2<(branchLength*0.35)**2){ok=false;break;}}
if(ok){const child=new Node(nx,ny,i);newNodes.push(child);n.children.push(nodes.length+newNodes.length-1);} // link parent→child only **once**
}
}
if(!newNodes.length) break;
nodes.push(...newNodes);
iterations++;
}
// --- Build root‑to‑leaf polylines (iterative to avoid deep recursion) ---
const paths=[];
const stack=[[0,[{x:nodes[0].x,y:nodes[0].y}]]];
while(stack.length){
const [idx,path]=stack.pop();
const n=nodes[idx];
if(!n.children.length){paths.push(path);continue;}
for(const c of n.children){
const nc=nodes[c];
stack.push([c,[...path,{x:nc.x,y:nc.y}]]);
}
}
// --- Chaikin smoothing helper ---
function chaikin(pts){
if(pts.length<3) return pts;
const out=[pts[0]];
for(let i=0;i<pts.length-1;i++){
const p=pts[i],q=pts[i+1];
out.push({x:0.75*p.x+0.25*q.x,y:0.75*p.y+0.25*q.y});
out.push({x:0.25*p.x+0.75*q.x,y:0.25*p.y+0.75*q.y});
}
out.push(pts[pts.length-1]);
return out;
}
function smooth(pts,it){for(let k=0;k<it;k++) pts=chaikin(pts);return pts;}
// --- Collect segments ---
const segments=[];
for(const p of paths){
let sp=p;
if(smoothPasses>0) sp=smooth(sp,smoothPasses);
for(let i=0;i<sp.length-1;i++) segments.push([sp[i].x,sp[i].y,sp[i+1].x,sp[i+1].y]);
}
// --- Draw ---
const turtle=new Turtle();
let si=0;
function walk(){
if(si>=segments.length) return false;
const s=segments[si++];
turtle.jump(s[0],s[1]);
turtle.goto(s[2],s[3]);
return true;
}