Boids: A Simulation of Flocking Behavior
Introduction
Boids is an artificial life program developed by Craig Reynolds in 1986 that simulates the flocking behavior of birds. The name “boid” is a shortened version of “bird-oid object”, which refers to a bird-like object. The simulation demonstrates how complex group behavior can emerge from 3 simple rules.
Craig Reynolds’ Flocking Theory
Reynolds proposed that flocking behavior emerges from three simple steering behaviors that each boid follows:
- Separation: Avoid crowding nearby flockmates by moving away from them
- Alignment: Steer towards wherever the flock is going
- Cohesion: Steer towards friends!
These three rules, when applied to each individual boid, create a realistic flocking behavior that mimics what we see in nature with birds, fish, and other animals.
Implementation Details
The way to do this is to evaluate the influence of each rule on what our direction will be; Every moment, the boid will need to average the direction of these three rules, and head that way. To visualize this, I used p5.js, a JavaScript library for creative coding.
In this implementation, each boid is represented by the Boid
class with some necessary information:
class Boid {
constructor(x, y, i) {
this.position = createVector(x, y); // the boid's position
this.velocity = p5.Vector.random2D(); // the boid's velocity
this.velocity.setMag(random(2, 5)); // the boid's velocity magnitude
this.acceleration = createVector(); // the boid's acceleration
this.i = i; // the boid's index in our array
this.maxForce = 0.05; // the boid's maximum force
this.maxSpeed = 6; // the boid's maximum speed (for visual purposes)
}
The three core flocking behaviors are implemented as separate methods:
1. Alignment
To align with the flock, we can just average the velocity of our neighbors.
align(boids) {
let visibleBoids = 0;
let total = createVector(); // the sum of all the velocities
for (let boid of boids) {
// this boid is not itself and is close enough, add it to the total
if (boid != this && dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < alignmentRadius) {
total.add(boid.velocity);
visibleBoids++;
}
}
// if there are any neighbors, average up the velocities, slow it down, and subtract our current velocity
if (visibleBoids > 0) {
total.div(visibleBoids);
total.setMag(this.maxSpeed);
total.sub(this.velocity);
total.limit(this.maxForce);
}
return total;
}
2. Separation
To avoid collisions, we can steer away from nearby boids. What we can do here is
separate(boids) {
let close = createVector(); // the sum of all the forces
let visibleBoids = 0; // the number of boids we're close to
for (let boid of boids) {
let d = dist(this.position.x, this.position.y, boid.position.x, boid.position.y); # the distance between the boids
if (boid != this && d < avoidanceRadius) { // if this boid is not itself and is close enough, add it to the total
let diff = p5.Vector.sub(this.position, boid.position); // the distance between this neighbor and ourself
diff.div(d*d); // we don't want to steer too hard, so we divide by the distance squared
close.add(diff); // add the difference to the total velocity we will steer towards
visibleBoids++;
}
}
// same deal, if there are any neighbors, average up the velocities, slow it down, and subtract our current velocity
if (visibleBoids > 0) {
close.div(visibleBoids);
close.setMag(this.maxSpeed);
close.sub(this.velocity);
close.limit(this.maxForce);
}
return close;
}
3. Cohesion
To steer towards the flock, we can get the average position of our neighbors, and then steer towards that. We’ll end up with a vector that points towards where our friends are.
cohesion(boids) {
let total = createVector();
let visibleBoids = 0;
// add up our neighbors' positions
for (let boid of boids) {
if (boid != this && dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < cohesionRadius) {
total.add(boid.position);
visibleBoids++;
}
}
// if there are any neighbors, average up the positions, subtract our position to get the distance, and steer towards the average position
if (visibleBoids > 0) {
total.div(visibleBoids);
total.sub(this.position);
total.setMag(this.maxSpeed);
total.sub(this.velocity);
total.limit(this.maxForce);
}
return total;
}
Conclusion
Thanks to The Coding Train for covering this topic in detail, and for the inspiration to make this project.