Based on sagejenson.com/physarum
Interesting variations:
- Physarum (variation)
- Physarum (variation)
- Physarum (variation)
Log in to post a comment.
// LL 2021 // Based on https://sagejenson.com/physarum const particles = 500; // min=1 max=5000 step=1 const grid_size = 300; // min=20 max=1000 step=1 const iterations = 2000; // min=0 max=100000 step=1 const follow_trails = 1; // min=0 max=1 step=1 (No,Yes) const formation = 0; // min=0 max=3 step=1 (Random,Grid,Circle,Clover) const draw_on_turn = 0; // min=0 max=1 step=1 (No,Yes) const diffuse = 1; // min=0 max=1 step=1 (No,Yes) const decay = 0.99; // min=0.0 max=1 step=0.01 const seed=0; // min=0 max=100 step=1 const opacity = -0.4; // min=-1 max=1 step=0.001 Canvas.setpenopacity(Math.sign(opacity) * Math.abs(opacity**4)); const grid = Array.from({length: grid_size * grid_size}); const grid2 = Array.from({length: grid_size * grid_size}); const particle_list = Array.from({length: particles}); const EPS = 0.001; const canvas_size = 200; const travel_distance = grid_size / canvas_size / 20; const sensor_distance = grid_size / canvas_size / 0.125; const travel_angle = Math.PI / 12; const sensor_angle = Math.PI / 4; const turtle = new Turtle(); var rng; function walk(i, t) { if (i==0) { rng = new RNG(seed); init_grid(); init_particles(); } particle_list.forEach(p => p.draw()); update_grid(); particle_list.forEach(p => p.move()); particle_list.forEach(p => p.deposit()); const iterations_t = iterations * t; return i+1 < iterations_t; } function init_particles() { const r = (formation == 2) ? 0.4 : 0.8; for (var i=0; i<particles; i++) { var x = random() * canvas_size - canvas_size / 2; var y = random() * canvas_size - canvas_size / 2; var a = random() * Math.PI * 2; if (formation == 1) { x = Math.round(x / 40) * 40; y = Math.round(y / 40) * 40; } else if (formation >= 2) { const len = Math.hypot(x, y); if (len > 0.01) { x = x / len * canvas_size * (r + random() * 0.1); y = y / len * canvas_size * (r + random() * 0.1); } } particle_list[i] = new Particle(x, y, a); } } function init_grid() { for (var j=0; j<grid_size; j++) { for (var i=0; i<grid_size; i++) { var value = 0; // if ((i % 50) == 0) value = 100; // if ((j % 50) == 0) value = 100; grid[j*grid_size+i] = value; } } } function update_grid() { if (!follow_trails) return; // Diffuse for (var j=0; j<grid_size; j++) { for (var i=0; i<grid_size; i++) { var value = 0, count = 0; for (var dy=-diffuse; dy<=diffuse; dy+=1) { for (var dx=-diffuse; dx<=diffuse; dx+=1) { const factor = ((Math.abs(dx) + Math.abs(dy)) < EPS) ? 10 : 1; const px = (i + dx + grid_size) % grid_size; const py = (j + dy + grid_size) % grid_size; value += grid[px + py * grid_size] * factor; count += factor; } grid2[i + j * grid_size] = value / count; } } } // Decay for (var gi=0; gi<grid.length; gi++) { var value = grid2[gi] * decay; if (value < 0.01) value = 0; grid[gi] = value; } } class Particle { constructor(x, y, a) { this.x = x; this.y = y; this.dir = a; this.need_to_draw = false; } move() { if (follow_trails) { const values = [0, 0, 0]; const factors = [-1, 0, 1]; factors.forEach((f, id) => { const x = this.x + sensor_distance * Math.cos(this.dir + f * sensor_angle); const y = this.y + sensor_distance * Math.sin(this.dir + f * sensor_angle); const dx = ((x / canvas_size + 0.5) * grid_size) | 0; const dy = ((y / canvas_size + 0.5) * grid_size) | 0; const i = dx + dy * grid_size; if (i >= 0 && i < grid_size*grid_size) { values[id] = grid[i]; } }); this.need_to_draw = false; if (values[1] > values[0]+EPS && values[1] > values[2]+EPS) { // No change } else if (Math.abs(values[0] - values[1]) < EPS) { // Random turn this.dir += travel_angle * (random() * 2 - 1); } else { // Turn towards stronger scent this.dir += Math.sign(values[2] - values[0]) * travel_angle; this.need_to_draw = true; } } else { this.dir += travel_angle * (random() * 2 - 1); } this.x += travel_distance * Math.cos(this.dir); this.y += travel_distance * Math.sin(this.dir); if (Math.abs(this.x) > canvas_size / 2) { this.x = (this.x + canvas_size * 1.5) % canvas_size - canvas_size / 2; } if (Math.abs(this.y) > canvas_size / 2) { this.y = (this.y + canvas_size * 1.5) % canvas_size - canvas_size / 2; } } deposit() { const dx = ((this.x / canvas_size + 0.5) * grid_size) | 0; const dy = ((this.y / canvas_size + 0.5) * grid_size) | 0; const i = dx + dy * grid_size; if (i >= 0 && i < grid_size*grid_size) { grid[i] += 1; } } draw() { if (draw_on_turn && !this.need_to_draw) return; const r = canvas_size / 1000; const step = 8; turtle.jump(this.x + r, this.y); for (var a=1/step; a<1+0.5/step; a+=1/step) { turtle.goto( this.x + r * Math.cos(a * Math.PI * 2), this.y + r * Math.sin(a * Math.PI * 2) ); } } } //function random() { return Math.random(); } function random() { return rng.nextFloat(); } // 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)}