Overview

A 3D implementation of Craig Reynolds’ Boids algorithm (1986) — a simulation of coordinated animal motion such as bird flocks and fish schools.

Each boid (bird-oid) follows three simple rules:

  1. Separation — Avoid crowding neighbors
  2. Alignment — Steer towards average heading of neighbors
  3. Cohesion — Steer towards average position of neighbors

Demo

Boids: 50

How It Works

Boid Structure

class Boid {
  position = new THREE.Vector3();
  velocity = new THREE.Vector3();
  acceleration = new THREE.Vector3();
  maxSpeed = 0.3;
  maxForce = 0.05;
  
  constructor() {
    // Random starting position and velocity
    this.position.set(
      Math.random() * 40 - 20,
      Math.random() * 20 - 10,
      Math.random() * 40 - 20
    );
    this.velocity.set(
      Math.random() * 2 - 1,
      Math.random() * 2 - 1,
      Math.random() * 2 - 1
    );
  }
}

Separation

Steer away from nearby boids to avoid collision.

separation(boids) {
  const perceptionRadius = 3;
  const steering = new THREE.Vector3();
  let total = 0;
  
  for (const other of boids) {
    const distance = this.position.distanceTo(other.position);
    if (other !== this && distance < perceptionRadius) {
      const diff = new THREE.Vector3()
        .subVectors(this.position, other.position);
      diff.divideScalar(distance * distance); // Weight by distance
      steering.add(diff);
      total++;
    }
  }
  
  if (total > 0) {
    steering.divideScalar(total);
    steering.setLength(this.maxSpeed);
    steering.sub(this.velocity);
    steering.clampLength(0, this.maxForce);
  }
  
  return steering;
}

Alignment

Match velocity with nearby boids.

align(boids) {
  const perceptionRadius = 5;
  const steering = new THREE.Vector3();
  let total = 0;
  
  for (const other of boids) {
    const distance = this.position.distanceTo(other.position);
    if (other !== this && distance < perceptionRadius) {
      steering.add(other.velocity);
      total++;
    }
  }
  
  if (total > 0) {
    steering.divideScalar(total); // Average velocity
    steering.setLength(this.maxSpeed);
    steering.sub(this.velocity);
    steering.clampLength(0, this.maxForce);
  }
  
  return steering;
}

Cohesion

Steer towards the average position of nearby boids.

cohesion(boids) {
  const perceptionRadius = 5;
  const steering = new THREE.Vector3();
  let total = 0;
  
  for (const other of boids) {
    const distance = this.position.distanceTo(other.position);
    if (other !== this && distance < perceptionRadius) {
      steering.add(other.position);
      total++;
    }
  }
  
  if (total > 0) {
    steering.divideScalar(total); // Center of mass
    steering.sub(this.position); // Vector to center
    steering.setLength(this.maxSpeed);
    steering.sub(this.velocity);
    steering.clampLength(0, this.maxForce);
  }
  
  return steering;
}

Combining Forces

flock(boids) {
  const alignment = this.align(boids);
  const cohesion = this.cohesion(boids);
  const separation = this.separation(boids);
  
  // Weight the forces (tune these for different behaviors)
  alignment.multiplyScalar(1.0);
  cohesion.multiplyScalar(1.0);
  separation.multiplyScalar(1.5); // Stronger to avoid collisions
  
  this.acceleration.add(alignment);
  this.acceleration.add(cohesion);
  this.acceleration.add(separation);
}

Update Loop

update() {
  // Apply acceleration to velocity
  this.velocity.add(this.acceleration);
  this.velocity.clampLength(0, this.maxSpeed);
  
  // Apply velocity to position
  this.position.add(this.velocity);
  
  // Reset acceleration for next frame
  this.acceleration.multiplyScalar(0);
  
  // Update mesh position and orientation
  this.mesh.position.copy(this.position);
  
  // Point mesh in direction of movement
  if (this.velocity.length() > 0) {
    const direction = this.velocity.clone().normalize();
    this.mesh.quaternion.setFromUnitVectors(
      new THREE.Vector3(0, 0, 1),
      direction
    );
  }
}

Performance Notes

  • Perception checks are O(n²) — for large flocks, use spatial partitioning (octree, grid)
  • Current implementation handles ~100 boids smoothly
  • Consider using instanced meshes for better performance with 1000+ boids

Tuning Parameters

// Perception radii
separationRadius = 3;   // Smaller = only avoid very close neighbors
alignmentRadius = 5;    // Medium = match nearby group heading
cohesionRadius = 5;     // Medium = stay with nearby group

// Force weights
separationWeight = 1.5; // Higher = avoid collisions more aggressively
alignmentWeight = 1.0;  // Standard
cohesionWeight = 1.0;   // Standard

// Physics
maxSpeed = 0.3;         // Maximum velocity
maxForce = 0.05;        // Maximum steering force

Extensions

  • Add obstacle avoidance
  • Target seeking behavior
  • Predator/prey dynamics
  • Wind/flow field influence
  • Trail rendering for motion visualization

References

  • Reynolds, C. W. (1987). “Flocks, herds and schools: A distributed behavioral model”
  • Red3d.com/cwr/boids — Original implementation