Back to projects
Mar 18, 2024
4 min read

Boids!

A simulation of bird-like flocking behavior

Boids: A Simulation of Flocking Behavior

boids

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:

  1. Separation: Avoid crowding nearby flockmates by moving away from them
  2. Alignment: Steer towards wherever the flock is going
  3. 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.