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