Article Swarm Intelligence · ≈ 9 min read

3D Flocking: Boids with Obstacles and a Leader

Reynolds' original three rules were built for a flat plane. Take the same flock into full 3D — through a cave system, around a skyscraper, behind a migrating leader — and every part of the algorithm from neighbour search to orientation needs an extra dimension of care.

TL;DR: Moving boids from 2D to 3D adds a third velocity axis, forces quaternion-based orientation with banking, and requires ray/sphere-cast obstacle avoidance plus a leader-following force so a flock can weave through obstacles while tracking a designated leader, all combined with priority rules so avoidance always beats social forces.

1. From 2D to 3D: the extra degree of freedom

The original 1986 Boids demo ran in a flat plane. Adding a third spatial dimension is more than "add a z". Every neighbour query must now search a volume of space instead of an area, meaning a uniform grid checks 27 neighbouring cells (3×3×3) instead of 9, and orientation is no longer a single heading angle θ but a full 3D rotation.

Birds, fish and drones also bank into turns — they roll around their direction of travel, not just yaw toward it. Capturing this requires either Euler angles (simple but suffers from gimbal lock) or quaternions (numerically stable, the standard choice in production 3D engines).

Same three rules still apply

Separation, alignment and cohesion carry over unchanged as 3D vector operations — see our 2D Boids article for the base rules. This article focuses on what is genuinely new in 3D: obstacles, a leader, and orientation.

2. Obstacle avoidance with ray casting

Static geometry — cave walls, buildings, terrain — must deflect the flock without breaking its cohesion. The standard technique casts one or more probe rays forward from each boid:

Obstacle probe ray = pos[i] + t · normalize(vel[i]), t ∈ [0, LOOKAHEAD]

hit = intersect(ray, obstacles)
if hit: v_avoid = reflect(vel[i], hit.normal) · weight_avoid
  • Single forward ray — cheap, but misses obstacles the boid is about to clip sideways.
  • Three-ray fan (centre + two offset by a small angle) — catches near-misses at a modest extra cost.
  • Sphere-cast — sweeps a sphere of the boid's own radius along the path, guaranteeing no part of the body clips the obstacle.

Obstacles themselves are almost always simplified to spheres or capsules for fast intersection tests, even when the visual mesh is complex — the physics collider and the render mesh are kept separate, exactly as in game engines.

3. Leader-following

To make a flock follow a specific route — migrating over a mountain range, escorting a player character — one or more boids are designated leaders. Leaders move along an independently scripted path (a Bézier or Catmull-Rom spline, or direct player/AI control) and are excluded from the standard flocking forces.

Leader-following force v_leader(i) = (pos[leader] − offset − pos[i]) · w_leader

where offset places followers slightly behind and beside the leader (avoiding a "conga line" of boids stacking directly behind it), and w_leader is typically weighted higher than ordinary cohesion.

Followers still apply separation and alignment among themselves, so the flock maintains its organic, breathing shape while tracking the leader's overall trajectory — this is the technique behind most "follow the guide bird" or escort-mission behaviours in games.

4. Combining rules and priorities

With five or six competing forces (separation, alignment, cohesion, obstacle avoidance, leader-following, boundary containment), naive summation can produce a net force that still drives a boid straight into a wall if cohesion is strong enough. Two robust combination strategies:

  • Weighted sum with priority weights — obstacle avoidance gets a weight an order of magnitude larger than social rules, so it always dominates when active.
  • Priority override — evaluate rules in order; if obstacle avoidance produces a non-zero force above a threshold, use it exclusively and skip the remaining rules for this tick.

Priority override is more predictable and is what most production flocking systems (including game-engine crowd simulation) use in practice, because it guarantees hard constraints (never hit a wall) cannot be diluted by soft preferences (stay near the flock).

5. Banking and quaternion orientation

A boid's visual orientation should track both where it is going (forward vector = normalized velocity) and how sharply it is turning (bank/roll proportional to turn rate). The standard construction:

Orientation from velocity forward = normalize(vel[i])
right = normalize(cross(worldUp, forward))
up = cross(forward, right)
bankAngle = clamp(turnRate · BANK_FACTOR, -MAX_BANK, MAX_BANK)
orientation = lookRotation(forward, up) · rotateAroundForward(bankAngle)

Representing the result as a quaternion and using spherical linear interpolation (slerp) between frames avoids the discontinuities and gimbal lock that plague raw Euler-angle interpolation, giving smooth banking even during sharp manoeuvres.

6. Spatial partitioning in 3D

The same uniform-grid trick from 2D boids extends directly: cell size ≈ view radius, and each boid checks its 27 (3×3×3) neighbouring cells instead of 9. For very non-uniform flock densities (a tight formation punctuated by scattered stragglers), an octree adapts cell size locally and can outperform a uniform grid, at the cost of more complex insertion/removal bookkeeping each frame.

GPU-friendly layout

For 10,000+ boids in WebGL, positions are Morton-coded (Z-order curve interleaving x, y, z bits) and radix-sorted each frame, so spatially close boids end up contiguous in the GPU buffer — this keeps neighbour lookups coalesced and fast even in three dimensions.

7. Pseudocode

function stepBoids3D(boids, obstacles, leader, dt):

  for each boid i:

    // 1. Priority check: obstacle avoidance first
    avoidForce = probeObstacles(boid[i], obstacles, LOOKAHEAD)
    if length(avoidForce) > AVOID_THRESHOLD:
      a = avoidForce
    else:
      // 2. Standard flocking (3D vectors)
      neighbors = getNeighbors3D(i, VIEW_RADIUS)
      v_sep   = separation(i, neighbors)
      v_align = alignment(i, neighbors)
      v_coh   = cohesion(i, neighbors)

      // 3. Leader-following, if a leader is assigned
      v_leader = leader ? followLeader(i, leader) : 0

      a = W_SEP*v_sep + W_ALIGN*v_align + W_COH*v_coh + W_LEADER*v_leader

    // 4. Integrate motion
    vel[i] = clampLength(vel[i] + a * dt, MAX_SPEED)
    pos[i] += vel[i] * dt

    // 5. Update orientation with banking
    orientation[i] = computeBankedOrientation(vel[i], prevVel[i])

Typical constants: VIEW_RADIUS = 12, LOOKAHEAD = 6, AVOID_THRESHOLD = 0.1, W_LEADER = 1.5–2.0 (higher than cohesion so the flock reliably tracks the leader through turns).

Frequently Asked Questions

What changes when boids move from 2D to 3D?

Position and velocity gain a z-component, neighbour search must scan a volume of cells (27 instead of 9 for a uniform grid), and orientation is no longer a single heading angle but a full 3D rotation, usually a quaternion, that must track both travel direction and banking roll.

How does 3D obstacle avoidance work for boids?

The most common approach casts one or more probe rays from the boid in its direction of travel and tests them against sphere or capsule colliders. If a ray intersects within a look-ahead distance, an avoidance force perpendicular to the obstacle surface is added with a weight high enough to override cohesion and alignment.

How does leader-following differ from normal flocking?

One or more designated boids move along an independently scripted path and are excluded from standard flocking rules. Ordinary flock members add an extra cohesion-like term pulling them toward the leader's recent position, so the whole flock follows the leader's route while still applying separation and alignment among themselves.

Why use quaternions instead of Euler angles for boid orientation?
Euler angles suffer from gimbal lock — a loss of one rotational degree of freedom at certain orientations, causing visible jumps or locked rotation axes. Quaternions represent rotation without this singularity and support smooth spherical interpolation (slerp) between frames, which is essential for visually smooth banking during sharp turns.
What is the difference between weighted-sum and priority-override force combination?
Weighted-sum simply adds all forces together with fixed weights, which can still let a boid clip an obstacle if social forces are strong enough. Priority-override evaluates obstacle avoidance first; if it produces a significant force, that force is used exclusively for the tick, guaranteeing the hard constraint (never hit a wall) is never diluted by soft preferences like staying near the flock.
Why cast multiple rays instead of just one for obstacle detection?
A single forward ray only detects obstacles directly ahead of the boid's centre and can miss geometry the boid's body would still clip sideways, especially at high speed or near corners. A small fan of two or three rays offset by a modest angle, or a full sphere-cast that sweeps the boid's own collision radius along its path, catches these near-misses at only a modest extra computational cost.
Why does the leader-following offset avoid a straight-line "conga line"?
If followers simply target the leader's exact position, they queue up directly behind it in a single-file line, which looks unnatural and causes constant near-collisions. Adding a small lateral and vertical offset — often randomised per follower — spreads the flock into a natural V or cluster formation around the leader's path instead.
How does an octree improve on a uniform grid for 3D flocking?
A uniform grid divides space into equal-sized cells regardless of boid density, which wastes memory and iteration time in sparse regions. An octree recursively subdivides only where boids are actually clustered, adapting cell size to local density — this pays off for flocks with very uneven distribution (a tight core plus scattered stragglers) at the cost of more complex per-frame insertion and removal bookkeeping.
Can obstacle avoidance and leader-following conflict, and how is that resolved?
Yes — a leader's scripted path might lead directly toward an obstacle the follower would otherwise avoid. Because obstacle avoidance sits at the top of the priority order (or carries the largest weight), followers always deviate around obstacles even while under leader-following influence, then resume tracking the leader once clear.
How many boids can a 3D flocking simulation handle in real time?
With Morton-coded spatial hashing and radix-sort neighbour lookups implemented in WebGL compute-style shaders, modern browser-based 3D flocking simulations can sustain 10,000 or more boids at 60 fps on consumer GPUs, since neighbour search remains O(N) and each boid's update is fully data-parallel.
▶ Live Demo

🐦 Try the 3D boids simulation

Watch the flock weave around obstacles and follow a leader in real time — thousands of agents, WebGL-accelerated.

Open simulation →

🔗 Related Simulations

🐦Boids 🦤Bird Flock