🚧 Broken on Mobile 🏗️ Link to heading

 ‘Boids’ is an artificial life simulation invented by Craig Reynolds. It is an example of artificial life and revolves around a set of simple rules that allow for emergent behavior arising from the interaction of the ‘boids’ in the simulation. The basic rules that define the simulation behavior are outlined below:

  • Separation: Boids avoid crashing into other boids.
  • Coherence: Boids move to centralise themselves in the flock .
  • Alignment: Boids attempt to match their velocity to those around them.

Check it out below, written in JavaScript:

 Let us look at some of the code that allows this to happen 🧐. Firstly, each boid is an object and all interactions are method driven. The constructor firstly initialises the position, velocity, acceleration and colour for each boid, which is pulled from a defined palette.

constructor() {
    this.pos = [0, 0, 0];
    this.vel = [0, 0, 0];
    this.acc=[0,0,0];
    this.col = palette[Math.round(Math.random() * 3)];
    this.randomise();
}

 The coherence behaviour is defined by the following method:

//fly towards center of flock
cohere() {
    let center = [0, 0, 0];
    let friends = 0;
    for (let otherBoid of boids) {
        if (this.distance(otherBoid) < visualRange) {
            if (this.col == otherBoid.col) {
                for (let k = 0; k < 3; k++) {

                    center[k] += otherBoid.pos[k];
                }
                friends += 1;
            }
        }
    }
    if (friends) {
        for (let k = 0; k < 3; k++) {
            center[k] = center[k] / friends;
            this.vel[k] += (center[k] - this.pos[k]) * coherenceFactor;
        }
    }
}

 Aligment…

//match velocity with flock
align() {
    let velocity = [0, 0, 0];
    let friends = 0;
    for (let otherBoid of boids) {
        if (this.distance(otherBoid) < visualRange) {
            if (this.col == otherBoid.col) {
                for (let k = 0; k < 3; k++) {
                    velocity[k] += otherBoid.vel[k];
                }
                friends += 1;
            }
        }
    }
    if (friends) {
        for (let k = 0; k < 3; k++) {
            velocity[k] = velocity[k] / friends;
            this.vel[k] += (velocity[k] - this.vel[k]) * alignmentFactor;
        }
    }
}

 And finally separation…

//avoid collisions with flock mates
separate() {
    let move = [0, 0, 0];
    for (let otherBoid of boids) {
        if (otherBoid != this) {
            if (this.distance(otherBoid) < minDistance) {
                for (let k = 0; k < 3; k++) {
                    move[k] += this.pos[k] - otherBoid.pos[k];
                }
            }
        }
    }
    for (let k = 0; k < 3; k++) {
        this.vel[k] += move[k] * separationFactor;
    }
}

 These three rules, implemented as separate functions operate on every boid in the simulation. Importantly, they are not equally weighted and each will influence the simulation differently, allowing specific simulations to be ‘tuned’, similar to a PID controller. Additional rules can be implemented to the simulation to provide more complex operation such as obstacle avoidance, however I will not explore these concepts here. You can randomise the above simulation using ‘r’ and pause using ‘p’.


Some additional additional reading material(s):