Demo
Press [Space Bar] to restart, [S] to save image.
Here's a link to my sketch: https://editor.p5js.org/interfinity/sketches/93iPmAb2s
Overview
This project resulted very differenly from what I first tried. Exploring the movement of particles, I came across the Boids Algorithm, which was first used for simulating the behavior of birds flying in groups: they follow the average direction of the others, but keeps a minimum distance so that they don't collide into each other. But instead of simply drawing out the particles moving in this behavior, I wanted to try something different. I first try to connect particles that are close to each other with lines, creating a mesh pattern, and I experimented with drawing some static particles which will repel the moving ones.
But eventrually, I discovered that the particles moving by the Boids Algorithm have a smooth but random curve path, so I tried to draw a thin white line from each particle to the center of the canvas, creating this radiant pattern, which gives a sense of stars, flowers, aurora, etc. Since I gave the lines an opacity, I have to limit the time in which they are drawn. After some testing, I set the time limit to be 2500ms (2.5s), which gives the best results.
Process
I first implimented the Boids Algorithm in JavaScript, creating a Boid class.
class Boid {
constructor(x, y, ratio) {
this.position = createVector(x, y);
this.ratio = ratio
this.velocity = createVector(random(-2 * ratio, 2 * ratio), random(-2 * ratio, 2 * ratio));
this.acceleration = createVector(0, 0);
this.maxSpeed = 3 * ratio;
this.maxForce = 0.05 * ratio;
this.neighborDist = 50 * ratio;
this.desiredSeparation = 25 * ratio;
}
flock(boids) {
let sep = this.separate(boids);
let ali = this.align(boids);
let coh = this.cohesion(boids);
sep.mult(1.5);
ali.mult(1.0);
coh.mult(1.0);
this.acceleration.add(sep);
this.acceleration.add(ali);
this.acceleration.add(coh);
this.centerConnect()
}
separate(boids) {
let steer = createVector(0, 0);
let count = 0;
for (let other of boids) {
let d = dist(
this.position.x,
this.position.y,
other.position.x,
other.position.y
);
if (other != this && d > 0 && d < this.desiredSeparation) {
let diff = p5.Vector.sub(this.position, other.position);
diff.normalize();
diff.div(d);
steer.add(diff);
count++;
}
}
if (count > 0) {
steer.div(count);
}
if (steer.mag() > 0) {
steer.normalize();
steer.mult(this.maxSpeed);
steer.sub(this.velocity);
steer.limit(this.maxForce);
}
return steer;
}
align(boids) {
let sum = createVector(0, 0);
let count = 0;
for (let other of boids) {
let d = dist(
this.position.x,
this.position.y,
other.position.x,
other.position.y
);
if (other != this && d > 0 && d < this.neighborDist) {
sum.add(other.velocity);
count++;
}
}
if (count > 0) {
sum.div(count);
sum.normalize();
sum.mult(this.maxSpeed);
let steer = p5.Vector.sub(sum, this.velocity);
steer.limit(this.maxForce);
return steer;
}
return createVector(0, 0);
}
cohesion(boids) {
let sum = createVector(0, 0);
let count = 0;
for (let other of boids) {
let d = dist(
this.position.x,
this.position.y,
other.position.x,
other.position.y
);
if (other != this && d > 0 && d < this.neighborDist) {
sum.add(other.position);
count++;
}
}
if (count > 0) {
sum.div(count);
return this.steerTowards(sum);
}
return createVector(0, 0);
}
steerTowards(target) {
let desired = p5.Vector.sub(target, this.position);
desired.normalize();
desired.mult(this.maxSpeed);
let steer = p5.Vector.sub(desired, this.velocity);
steer.limit(this.maxForce);
return steer;
}
centerConnect() {
stroke(255, 25);
strokeWeight(1.5 * this.ratio);
line(this.position.x, this.position.y, width / 2, height / 2);
}
update() {
this.prevPosition = this.position;
this.velocity.add(this.acceleration);
this.velocity.limit(this.maxSpeed);
this.position.add(this.velocity);
this.acceleration.mult(0);
}
}
This class represents a Boid particle which draws a thin line between itself and the center of canvas every time the flock() method is called. Then, in the sketch.js file, I simply wrote a function reset() which creates 100 Boid particles and use the setTimeout() function to stop drawing in 2.5 seconds.
let boids = [];
let isDrawing = false;
function setup() {
let canvas = createCanvas(
Math.min(windowWidth, windowHeight),
Math.min(windowWidth, windowHeight)
);
reset();
}
function draw() {
for (let boid of boids) {
boid.flock(boids);
boid.update();
moveOver(boid);
}
}
function moveOver(b) {
if (b.position.x > width) b.position.x -= width;
if (b.position.x < 0) b.position.x += width;
if (b.position.y > height) b.position.y -= height;
if (b.position.y < 0) b.position.y += height;
}
function reset() {
isDrawing = true;
for (let i = 0; i < 100; i++) {
boids.push(
new Boid(random(width), random(height), Math.min(width / 800, 1))
);
}
setTimeout(() => {
boids = [];
isDrawing = false;
}, 2500);
background(15);
}
function keyPressed() {
if (keyCode == 83) {
saveCanvas("polar_radiance.png");
} else if (keyCode == 32 && !isDrawing) {
reset();
}
}
Also, the moveOver() function makes sure that when a Boid particles moves beyond an edge of the canvas, it returns to the opposite edge. And I used the keyPressed() function to allow users to press the [Space Bar] to restart the program and press [S] to save the canvas as an image.
Conclusion
Although I don't think this is the perfect use case for the Boids Algorithm, it is certainly an interesting experiment with it. Here are some results I got:
