{
restart;
const [w, h] = [800, 600];
const canvas = htl.html`<canvas width=${w} height=${h}>`;
const ctx = canvas.getContext("2d");
const size = 10;
const r = size / 2;
const n = nparticles;
const s = nspecies;
const dmin = minInteractionDist;
class Particle {
constructor() {
this.x = Math.random() * w;
this.y = Math.random() * h;
const ang = Math.random() * Math.PI * 2;
this.dx = Math.cos(ang);
this.dy = Math.sin(ang);
this.species = ~~(Math.random() * s);
this.color = `hsl(${(this.species * 360) / s},100%,50%)`;
}
tick() {
this.x = (this.x + this.dx + w) % w;
this.y = (this.y + this.dy + h) % h;
this.dx *= 0.99;
this.dy *= 0.99;
}
display() {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, r, 0, Math.PI * 2);
ctx.fill();
}
}
const particles = d3.range(n).map(() => new Particle());
const laws = d3.range(s).map(() => d3.range(s));
for (let i = 0; i < s; i++)
for (let j = i; j < s; j++) laws[i][j] = laws[j][i] = Math.random() * 6 - 3;
while (true) {
ctx.fillStyle = "rgba(0,0,0,0.1)";
ctx.fillRect(0, 0, w, h);
for (let p of particles) {
p.tick();
p.display();
}
for (let i = 0; i < n; i++) {
const p = particles[i];
for (let j = i + 1; j < n; j++) {
const q = particles[j];
let [fx, fy] = [p.x - q.x, p.y - q.y];
const d = Math.hypot(fx, fy);
if (d < dmin) {
let mag = 0.005 - 0.005 * (d / dmin);
let collision = false;
if (d < size) {
const coll = 1 - d / size;
[fx, fy] = [(fx / d) * coll, (fy / d) * coll];
} else {
mag *= laws[p.species][q.species];
[fx, fy] = [(fx / d) * mag, (fy / d) * mag];
}
[p.dx, p.dy] = [p.dx + fx, p.dy + fy];
[q.dx, q.dy] = [q.dx - fx, q.dy - fy];
}
}
}
yield canvas;
}
}