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; }