Boid-like behavior
Log in to post a comment.
// You can find the Turtle API reference here: https://turtletoy.net/syntax Canvas.setpenopacity(.25); // Global code will be evaluated once. const turtle = new Turtle(); let numSteps = 50000; let numParticles = 55; let numTargets = 3; // min 2 target let minDist = 150; let pInitRange=5; let aVal = .05; let vVal = .05; let vLimit = .5; let aNoise = .0; let vNoise = .0; let pNoise = .0; let checkInterval = 500; let checkAccelInterval = 10000; let randomDeath = 10; let showCircles = false; let showProgress = false; // if false, will create optimized paths for saving svg function rrange(min,max) { return (Math.random())*(max-min)+min; } function randomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } // vec2 functions function scl(a,b) { return [a[0]*b, a[1]*b]; } function madd(a,b,v) { return [a[0]+v*b[0], a[1]+v*b[1]]; } function add(a,b) { return [a[0]+b[0], a[1]+b[1]]; } function sub(a,b) { return [a[0]-b[0], a[1]-b[1]]; } function div(a,v) { return [a[0]/v, a[1]/v ]; } function mult(a,b) { return [a[0]*b[0], a[1]*b[1] ]; } function dot(a,b) { return a[0]*b[0] + a[1]*b[1]; } function len(a) { return Math.sqrt(a[0]**2 + a[1]**2); } function nrm(a) { return scl(a, 1/len(a)); } function lrp(a,b,f) { return [a[0]*f+b[0]*(1-f), a[1]*f+b[1]*(1-f)]; } function angle(a, b) { return Math.atan2(a[1] - b[1], a[0] - b[0]); } function distance(a, b) { return len(sub(a, b)) }; function lerp(a, b, t) { return [(1-t)*a[0] + t*b[0], (1-t)*a[1] + t*b[1] ]; } function v2randrange(min,max) { return[rrange(min,max), rrange(min,max)]; } function v2init(val) { return[val, val]; } function cosin(val) { return[Math.cos(val), Math.sin(val)]; } class Particle { constructor(istarget=false) { this.istarget=istarget; this.allPos =[]; this.reset(); } setAsTarget() { this.istarget=true; } reset() { this.pos = v2randrange(-60,60); this.prev = add(this.pos, [0,0]); this.v = [0,0]; // v2randrange(-.1,.1); this.a = [0,0]; this.accelRate = rrange(.5*aVal, aVal); this.other = 0; this.center = v2randrange(-30, 30); this.radius = [rrange(30,50), rrange(30,50)]; this.phase = rrange(0, 2*Math.PI); this.freq = rrange(-.002, .002); this.simulate(false,false); this.simulate(false,false); } simulate(check=false, addPos=true, addNoise) { if (this.istarget) { this.prev = this.pos; this.pos = add(this.center, mult(this.radius, cosin(this.phase))); this.phase += this.freq; } else { if (false && this.other != -1) { this.prev = this.pos; //console.log('this is', this.other); //console.log('targets length', targets.length); this.pos = add(targets[this.other].pos, v2randrange(-13,13)); return; } if (check && this.other != -1) { this.a = nrm(sub(targets[this.other].pos, this.pos)); } this.a = madd(this.a, v2randrange(-aNoise,aNoise), 1); this.prev = this.pos; this.v = madd(this.v, this.a, aVal); { let rangle = randomInt(0,3) * Math.PI / 2; let c = [vNoise * Math.cos(rangle), vNoise * Math.sin(rangle)]; //let v = add(this.v, v2randrange(-vNoise, vNoise)) let v = madd(this.v, c, vVal); let dv = len(v); if (dv> vLimit) { v = scl(v, .5*vVal/dv); } v = madd(v, v2randrange(-pNoise,pNoise), 1); this.pos = add(this.pos, v); } //if (false ) // this.pos = madd(this.pos, this.v, vVal); // // if (addPos) this.allPos.push(this.pos); } } randomize() { this.v = add(this.v, v2randrange(-15,15)); } drawIt() { turtle.penup(); turtle.jump(this.prev[0], this.prev[1]); turtle.pendown(); turtle.goto(this.pos[0], this.pos[1]); } drawPath() { turtle.penup(); turtle.jump(this.allPos[0]); turtle.pendown(); for (let i=0; i < this.allPos.length; i++) { turtle.goto(this.allPos[i]); } turtle.penup(); } } function FindNeighbor(minDist) { let i = randomInt(0, numParticles-1); { //particles[i].other = -1; let minIndex = -1; let cMinDist = 1000000; for (let k=0; k < targets.length; k++) { // let j = Math.floor(Math.random() * targets.length); //particles[i].other = j; //break; //if (false) let d = distance(particles[i].pos, targets[k].pos); if (d< minDist && d < cMinDist ) { cMinDist = d; minIndex = k; } } if (minIndex != -1) { particles[i].other = minIndex; } } } let particles = []; let targets = []; console.log('Start', numTargets); console.log('1-targets length', targets.length); for (let i=0; i < numTargets; i++) { let p = new Particle(true); targets.push(p); } console.log('2-targets length', targets.length); for (let i=0; i < numParticles; i++) { let p = new Particle(); let pi = randomInt(0, numTargets-1); do { let qi = randomInt(0, numTargets-1); if (qi != pi) { p.other = qi; break; } } while (true); p.pos = add(targets[pi].pos, v2randrange(-pInitRange,pInitRange)); p.prev = add(p.pos, [0,0]); particles.push(p); } // The walk function will be called until it returns false. function walk(step) { if (showProgress) { for (let i=0; i < numParticles; i++) { particles[i].drawIt(); } if (showCircles && step > 2) for (let i=0; i < numTargets; i++) { targets[i].drawIt(); } } if (step > 1 && (step % checkInterval) == 0) FindNeighbor(minDist); if (false) if ((step % randomDeath) == 0) { let pi = randomInt(0, numParticles-1); particles[pi].reset(); } for (let i=0; i < numTargets; i++) { targets[i].simulate(); } let check = false; if ((step % checkAccelInterval) == 0) check = true; check = true; for (let i=0; i < numParticles; i++) { particles[i].simulate(check); } if (!showProgress && step == numSteps ) { for (let i=0; i < numParticles; i++) { particles[i].drawPath(); } return false; } return (step < numSteps); }