xxxxxxxxxx
88
//
// Self-propelled particles
//
// Modeled on https://www.nature.com/articles/srep37969
//
let particles = [];
let torus = true;
let v = 5.0; // Constant velocity, pixels/step
function setup() {
createCanvas(windowWidth, windowHeight);
for (let i = 0; i < 2000; i++) {
particles.push(new Particle());
}
}
function draw() {
background(220);
for (let particle of particles) {
particle.update();
particle.show();
}
}
function left(pos1, vel1, pos2) {
let relPos = p5.Vector.sub(pos2, pos1);
let crossProduct = relPos.x * vel1.y - relPos.y * vel1.x;
return crossProduct > 0;
}
function rotateVelocity(vel, angle) {
let radianAngle = radians(angle);
let newVel = createVector(
vel.x * cos(radianAngle) - vel.y * sin(radianAngle),
vel.x * sin(radianAngle) + vel.y * cos(radianAngle)
);
return newVel;
}
class Particle {
constructor() {
this.pos = createVector(random(width), random(height));
this.vel = createVector(0, v);
this.vel = rotateVelocity(this.vel, random(360.0) );
}
update() {
let leftCount = 0;
let rightCount = 0;
let alpha = 180.0; // constant turn
let beta = 17.0; // strength of neighor influence
let radius = 50; // radius of detection (pixels)
for (let other of particles) {
if ((other !== this) && (p5.Vector.sub(other.pos, this.pos).mag() < radius)) {
if (left(this.pos, this.vel, other.pos)) leftCount++;
else rightCount++;
}
}
if (rightCount > leftCount) {
this.vel = rotateVelocity(this.vel, alpha + beta*(rightCount + leftCount));
} else {
this.vel = rotateVelocity(this.vel, alpha - beta*(rightCount + leftCount));
}
this.pos.add(this.vel);
if (torus) {
this.pos.x = (this.pos.x + width) % width;
this.pos.y = (this.pos.y + height) % height;
} else {
// Bounce off edges
if (this.pos.x < 0 || this.pos.x > width) {
this.vel.x *= -1;
}
if (this.pos.y < 0 || this.pos.y > height) {
this.vel.y *= -1;
}
}
}
show() {
stroke(0);
strokeWeight(2);
fill(175);
ellipse(this.pos.x, this.pos.y, 6, 6);
}
}