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