One of the things I love about natural systems is how complex behavior can arise from sets of simple rules. In biology you see this fairly often, for example Lotka-Volterra equations predict predator-prey interactions and can lead to very complex outcomes despite being incredibly simple. A famous example from the history of computer science is John Conway’s Game of Life which produces incredibly complex (and sometimes unpredictable) interactions between cellular automata from only four rules. A lesser known example are Boids, or bird-oid objects, which demonstrate how three principles, separation, alignment, and cohesion can explain how flocking behavior can arise despite decentralized control. I implemented my own version of the Boids algorithm in Typescript using the HTML5 Canvas element. The complete code can be found in the Github repo.
You can play with the demo before jumping into the code:
Understanding Boids
The flocking behavior in boids is an emergent property of three different rules:
- Try to stay near the central position of your neighbors (cohere)
- Try to get away from very close neighbors (avoid)
- Try to move in the same direction as you neighbors (align)
While each of these rules is applied independently of one another, they lead to complex flocking behavior when taken together.
Implementation
To implement boids, I created two classes:
- A Boid class which has fields for its current x, y position on the canvas as well as its horizontal velocity, vx, and its vertical velocity vy.
- A BoidContainer which has fields for a list of boids, references to the canvas element and rendering context, and a list of values for the neighbor distance, avoid factor, align factor, etc. which are shared by all the boids.
I also added methods to have the boids avoid the walls of the container, collision detection, clamp boid speed to between a minimum and maximum, and some convenience methods to modify parameters while the simulation is running.
One thing I did not do is have boids detect collisions with each other, or optimize how the BoidContainer detects neighbors. In theory, something like an AABB tree (Axis Aligned Bounding Box) would make finding neighbors more efficient (currently, it checks the distance of each boid against every other boid). That being said, despite the O(n2) complexity, it seems to run fine. I might attempt another version with collision detection and a real data structure to detect distance to other boids so that I can push the number of boids to something obscene. I also might see if I can change the color of the boids based on the average distance to neighbors.