Gravity wells

Given a few gravity wells at random positions, spawn particles and trace their paths if they live long enough without crashing into a well.

Log in to post a comment.

// LL 2021

const turtle = new Turtle();

const gravity_wells = 2;     // min=2, max=10, step=1
const particles = 5000;      // min=1, max=10000, step=1
const opacity = 0.02;        // min=0.02, max=1, step=0.01
const gravity = 1000;        // min=1, max=10000, step=1
const seed = 0;              // min=0, max=1000, step=1
const iterations = 150;      // min=1, max=500, step=1
const scale = 1.02;          // min=0.1, max=10, step=0.1

const max_radius = 3;
const min_radius = 10;

var gravity_well_list = null;
var particle_count = 0;

const max_attempts = 1000000;

Canvas.setpenopacity(opacity);

var rng;

class vec2
{
    constructor(x, y) { this.x = x; this.y = y; }
    dup() { return new vec2(this.x, this.y); }
    set(v2) { this.x = v2.x; this.y = v2.y; }
    add(v2) { this.x += v2.x; this.y += v2.y; }
    sub(v2) { this.x -= v2.x; this.y -= v2.y; }
    length() { return Math.hypot(this.x, this.y); }
    normalize() { var length = this.length(); if (length > 0) { this.x /= length; this.y /= length; } }
    multiply_f(f) { this.x *= f; this.y *= f; }
}

class GravityWell {
    constructor(x, y, r) {
        this.position = new vec2(x, y);
        this.radius = r;
    }

    draw() {
        turtle.jump(this.position.x / scale, (this.position.y - this.radius) / scale);
        turtle.circle(this.radius / scale);
    }
}

class Particle {
    constructor(x, y, prev_x, prev_y) {
        this.current_position = new vec2(x, y);
        this.previous_position = new vec2(prev_x, prev_y);
        this.died = false;
        this.history = [this.current_position.dup()];
    }

    update() {
        var v = this.current_position.dup();
        v.sub(this.previous_position);
        this.previous_position.set(this.current_position);
       
        gravity_well_list.forEach(w => {
            var vgravity = this.current_position.dup();
            vgravity.sub(w.position);
            const distance = vgravity.length();
            if (distance > w.radius) {
                vgravity.multiply_f(-(gravity * w.radius)/1000/distance/distance);
                v.add(vgravity);
            } else {
                this.died = true;
            }
        });

        const friction = 0.99;
        v.multiply_f(friction);

        this.current_position.add(v);
        this.history.push(this.current_position.dup());
    }

    draw() {
        turtle.up();
        this.history.forEach(pos => {
            turtle.goto(pos.x / scale, pos.y / scale);
            turtle.down();
        });
    }
}

function plateau(n, level) {
    return Math.round(n/level)*level;
}

// The walk function will be called until it returns false.
function walk(i, t) {
    if (i==0) {
        rng = new RNG(seed);
    
        gravity_well_list = [];
        for (var j=0; j<gravity_wells; j++) {
            const r = rng.nextFloat() * (max_radius - min_radius) + min_radius;
            const x = (rng.nextFloat() - 0.5) * (200-r);
            const y = (rng.nextFloat() - 0.5) * (200-r);
            gravity_well_list.push(new GravityWell(x, y, r));
        }

        particle_count = 0;
    }

    const chance = rng.nextFloat();
    const x = 200 * (plateau(rng.nextFloat(), (chance < 0.5) ? 1 : 0.0625) - 0.5);
    const y = 200 * (plateau(rng.nextFloat(), (chance > 0.5) ? 1 : 0.0625) - 0.5);
    // const x = 200 * (plateau(rng.nextFloat(), 0.00025) - 0.5);
    // const y = -100;
    const vel = 0.25;
    const prev_x = x + (rng.nextFloat() - 0.5) * vel;
    const prev_y = y + (rng.nextFloat() - 0.5) * vel;
    const particle = new Particle(x, y, prev_x, prev_y);

    for (j=0; j<iterations*t && !particle.died; j++) {
        particle.update();
    }
    
    if (!particle.died) {
        particle.draw();
        particle_count++;
    }

    //sleep(10);

    return particle_count < particles && i < max_attempts;
}

////////////////////////
// Utils
////////////////////////

function sleep(milliseconds) { const start=Date.now(); while (Date.now()-start < milliseconds) {} }

///////////////

// Minified Random Number Generator from https://turtletoy.net/turtle/ab7a7e539e
function RNG(t){return new class{constructor(t){this.m=2147483648,this.a=1103515245,this.c=12345,this.state=t||Math.floor(Math.random()*(this.m-1))}nextFloat(){return this.state=(this.a*this.state+this.c)%this.m,this.state/(this.m-1)}}(t)}