While out on a stroll with a good friend, she mentioned a screensaver she had seen that simulated a flock of birds. It made me think of the boids algorithm, and the natural and mesmerizing flocking phenomena called murmuration. I couldn’t help but think about it for the rest of the day. So, I decided to try to implement a boids simulation in webgl so that I could stop thinking about it. Its easier to express myself in code than in words, so I figured I’d just write a blog post about it.
The Boid Algorithm was created by Craig Reynolds in 1986, the term “boid” stands for “bird-oid object”. I always think of a new yorker saying “bird” when I see the word “boid” and I prefer to pronounce it that way 💜. I think the only better option would be “birb”. The algorithm is a simple yet elegant model for simulating the coordinated motion of a flock of birds. However, its applications extend beyond bird flocking patterns, encompassing schools of fish, human crowds, and others!
Craig Reynolds introduced boids in the 1980s as a computer model for simulating the coordinated motion of flocks. Rather than controlling the entire flock’s movement, Reynolds’s model focused on the individual, allowing complex patterns to emerge naturally from the interaction of simpler agents following basic rules.
Each boid has a tiny brain, that maintains a state that includes its position, velocity, and acceleration. Based on this state and the state of its nearby neighbors (which is controlled by a perception distance), every boid makes decisions on where to move next. These decisions arise from three simple rules: cohesion, alignment, and separation.
Cohesion is the behavior that encourages boids to steer towards the average position of their local flockmates. This causes them to form groups and stay together.
function cohesion(boid, boids):
perception_radius = SOME_DISTANCE_VALUE // :) Usually larger than separation
steering_force = Vector(0,0,0)
total_in_perception = 0
for each other_boid in boids:
if distance(boid.position, other_boid.position) < perception_radius:
steering_force += other_boid.position
total_in_perception += 1
if total_in_perception > 0:
steering_force = steering_force / total_in_perception // average position
steering_force = steering_force - boid.position // direction towards average position
return steering_force
end function
Alignment makes a boid steer in the direction that matches the average heading of its local flockmates. This creates a uniform direction for a group of boids.
function alignment(boid, boids):
perception_radius = SOME_DISTANCE_VALUE // :) Usually larger than cohesion
average_velocity = Vector(0,0,0)
total_in_perception = 0
for each other_boid in boids:
if distance(boid.position, other_boid.position) < perception_radius:
average_velocity += other_boid.velocity
total_in_perception += 1
if total_in_perception > 0:
average_velocity = average_velocity / total_in_perception // average velocity
average_velocity = average_velocity.normalized() // desired direction
return average_velocity - boid.velocity // the steering force to align with others
end function
Separation ensures that boids don’t get too close to one another. It calculates a force that steers each boid away from its close neighbors.
def separation(current_boid, all_boids):
perception_radius = SOME_DISTANCE_VALUE
steering_force = Vector(0,0,0)
total_in_perception = 0
for other_boid in all_boids:
distance_to_other = distance(current_boid.position, other_boid.position)
if 0 < distance_to_other < perception_radius:
# Calculate the difference vector pointing from
# the other boid to this current_boid
difference = current_boid.position - other_boid.position
# Normalize the difference vector by the distance,
# so that farther boids have less influence
difference = difference / distance_to_other
# Add the difference vector to the steering force of
steering_force += difference
total_in_perception += 1
# If there were any boids within the perception radius
if total_in_perception > 0:
# Average the steering force by the number of boids within the perception radius
steering_force = steering_force / total_in_perception
# Return the steering force, which will be used to update the current_boid's velocity
return steering_force
The example separation
function above calculates the steering force for a single boid, current_boid
, based on its interactions with all other boids in the simulation, all_boids
. The function begins by setting a perception radius, which is the distance within which other boids will influence the current boid’s behavior.
If there were any boids within the perception radius, the steering force is averaged by the number of these boids. If there were no boids within the perception radius, the steering force remains zero, meaning the current boid’s velocity will not be adjusted based on separation behavior.
Finally, the function returns the steering force. This steering force is then added to the current boid’s velocity, influencing its direction and speed in the next time step of the simulation.
By combining the forces from these three rules, each boid determines its movement direction in the flock. This elegant trio of rules gives rise to the intricate and lifelike patterns that we witness when running a boid simulation. The magic is in the simplicity: while each individual follows basic logic, the collective exhibits complex, emergent behavior that mirrors natural phenomena. It’s beautiful, even in WebGL.
I’ve create an implementation on glitch that you can fork and modify, or just play with. I’ve also embedded it below.