Ollin

Flocking (boids)

A flock of boids is a crowd of simple agents that each follow local rules. Those rules produce lifelike flocking, so you get coherent groups that swirl, split, and regroup with no leader. Each boid steers by three forces, applied over the neighbors it can see, and the flocking motion emerges from them. This is Reynolds' classic model.

  • Separation steers away from crowding neighbors.
  • Alignment steers toward the average heading of nearby boids.
  • Cohesion steers toward the average position of nearby boids.
Diagram of one dark boid inside a faint circle labeled personal space, three gray neighbors pressing in, and an orange arrow labeled away from the crowd pointing out of the crush Diagram of one dark boid among gray neighbors inside a faint circle labeled what it can see, each neighbor with its own small heading arrow, and an orange arrow showing the average heading the boid turns toward Diagram of one dark boid inside a faint circle, gray neighbors clustered to one side, an orange ringed dot at their center of the group, and an orange arrow from the boid toward it

Boids is a stateful simulation, so you hold on to the instance and call step() on it each frame. Its live state is positions, velocities, and heading(_:), which you draw however you like. Give it a seed to get a reproducible flock.

Contents

Building and stepping

Boids(count: Int, in bounds: Rectangle, seed: Int = 0,
      maxSpeed: Double = 3, maxForce: Double = 0.12,
      perceptionRadius: Double = 50, separationRadius: Double = 22, margin: Double = 60)

The flock starts at random positions inside bounds. A boid steers back inward once it comes within margin of an edge. Step the flock in draw(), then read its state to draw each boid. drawBoids does that for you, drawing one triangle per boid, pointing along that boid's heading and filled with the current fill.

final class Flock: Sketch {
    let flock = Boids(count: 500, in: bounds, seed: 7)

    override func draw() {
        flock.step()
        background(.black); fill(.white)
        drawBoids(flock, size: 10)
    }
}

To color each boid separately, draw the triangles yourself from positions[i] and heading(i). A common choice is to tint by heading, so boids traveling the same way share a color. See the Flocking example.

Tuning the flock

The weights and the radii set the look, and all of them are vars on the instance:

ParameterEffect
separation / alignment / cohesionthe weight of each rule. Raise separation for looser flocks, and cohesion for tighter ones
perceptionRadiushow far a boid sees other boids for alignment and cohesion
separationRadiusthe shorter range inside which boids push apart
maxSpeedthe top speed a boid travels at
maxForcethe sharpest turn a boid can make. A lower value turns more smoothly and more slowly
marginhow far from an edge a boid starts to turn back
Three panels of small dark triangles. Separation only: an even scatter pointing every way. Plus alignment: one loose school all pointing the same way. Plus cohesion: three tight flocks gathered apart from each other

Neighbor lookups use a uniform spatial hash as their broad phase, so their cost stays close to linear in the flock size.

Following a flow field

Set field and fieldStrength to make the flock follow a FlowField as well. The flock then drifts along the flow while it keeps flocking:

let flock = Boids(count: 500, in: bounds, seed: 7)
flock.field = curlField(scale: 0.003)
flock.fieldStrength = 0.6

Related: Flow fields (the field a flock can follow), Differential growth (the other stateful, stepped generator), Physics (force-driven motion with collisions and joints).

↑↓ move ↩ open