Searching for the light

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

}