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