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