Dynamic Flocking

Concept

Here I tried to simulate flocking behavior by creating a “school” of boids that move together in a coordinated, natural pattern. Each boid follows three core rules—separation, alignment, and cohesion—to avoid crowding, match direction, and stay close to neighbors. Together, these rules give rise to a lifelike group movement, where each boid is influenced by its surroundings to form a cohesive, dynamic flock.

Code Breakdown

Each Boid has properties for position, velocity, and acceleration, and the draw loop continuously applies flocking behaviors, updates the boid’s position, and renders it on the canvas with a trailing effect for smooth movement. The flock method combines the three behaviors by calculating forces based on nearby boids within defined distances. The separation force keeps boids from colliding, alignment adjusts direction to match neighboring boids, and cohesion steers toward the group center. The wrapEdgesmethod ensures that boids reappear on the opposite side if they move off-screen. An interactive feature, mouseDragged, adds new boids at the mouse location when dragged, adding flexibility to the simulation.

let boids = []; // Array to store boid instances
let w = 600, h = 600;

function setup() {
  createCanvas(w, h);
  background(0);

  // Initialize boids with random positions
  for (let i = 0; i < 50; i++) {
    boids.push(new Boid(random(width), random(height)));
  }
}

function draw() {
  background(255, 5); // Slight trail effect
  
  boids.forEach(boid => {
    boid.flock(boids);  // Apply flocking behavior
    boid.update();      // Update position and velocity
    boid.wrapEdges();   // Wrap around edges of the canvas
    boid.show();        // Draw the boid
  });
}

// Constants for boid behavior
let M = 2;        // Max speed
let F = 0.28;     // Max force
let sepDist = 25; // Desired separation distance
let aliDist = 50; // Alignment neighbor distance
let cohDist = 150; // Cohesion neighbor distance

// Class to represent a single boid
class Boid {
  constructor(x, y) {
    this.position = createVector(x, y);
    this.velocity = createVector(random(-1, 1), random(-1, 1));
    this.acceleration = createVector(0, 0);
  }

  // Method to apply flocking behavior
  flock(boids) {
    let separation = this.separate(boids); // Separation
    let alignment = this.align(boids);     // Alignment
    let cohesion = this.cohesion(boids);   // Cohesion

    // Adjust weights for the forces
    separation.mult(1.5);
    alignment.mult(0.99);
    cohesion.mult(0.99);

    // Apply forces to acceleration
    this.acceleration.add(separation);
    this.acceleration.add(alignment);
    this.acceleration.add(cohesion);
  }

  // Update position based on velocity and acceleration
  update() {
    this.velocity.add(this.acceleration);
    this.velocity.limit(M); // Limit speed
    this.position.add(this.velocity);
    this.acceleration.mult(0); // Reset acceleration
  }

  // Wrap boids around the screen edges
  wrapEdges() {
    this.position.x = (this.position.x + w) % w;
    this.position.y = (this.position.y + h) % h;
  }

  // Draw boid as a small circle
  show() {
    fill(255, 0, 0);
    ellipse(this.position.x, this.position.y, 10);
  }

  // Separation behavior: Avoid crowding neighbors
  separate(boids) {
    let steer = createVector(0, 0);
    let count = 0;
    boids.forEach(other => {
      let distance = p5.Vector.dist(this.position, other.position);
      if (distance > 0 && distance < sepDist) {
        let diff = p5.Vector.sub(this.position, other.position);
        diff.normalize();
        diff.div(distance);
        steer.add(diff);
        count++;
      }
    });
    if (count > 0) steer.div(count);
    if (steer.mag() > 0) {
      steer.normalize();
      steer.mult(M);
      steer.sub(this.velocity);
      steer.limit(F);
    }
    return steer;
  }

  // Alignment behavior: Steer towards the average heading of local flockmates
  align(boids) {
    let sum = createVector(0, 0);
    let count = 0;
    boids.forEach(other => {
      let distance = p5.Vector.dist(this.position, other.position);
      if (distance > 0 && distance < aliDist) {
        sum.add(other.velocity);
        count++;
      }
    });
    if (count > 0) {
      sum.div(count);
      sum.normalize();
      sum.mult(M);
      let steer = p5.Vector.sub(sum, this.velocity);
      steer.limit(F);
      return steer;
    }
    return createVector(0, 0);
  }

  // Cohesion behavior: Steer towards the average position of local flockmates
  cohesion(boids) {
    let sum = createVector(0, 0);
    let count = 0;
    boids.forEach(other => {
      let distance = p5.Vector.dist(this.position, other.position);
      if (distance > 0 && distance < cohDist) {
        sum.add(other.position);
        count++;
      }
    });
    if (count > 0) {
      sum.div(count);
      return this.seek(sum);
    }
    return createVector(0, 0);
  }

  // Seek method to steer boid towards a target position
  seek(target) {
    let desired = p5.Vector.sub(target, this.position);
    desired.normalize();
    desired.mult(M);
    let steer = p5.Vector.sub(desired, this.velocity);
    steer.limit(F);
    return steer;
  }
}

// Function to add a new boid on mouse drag
function mouseDragged() {
  boids.push(new Boid(mouseX, mouseY));
}

Future Improvements

While the current simulation focuses on realistic flocking dynamics, future enhancements will center around visual and auditory immersion. Adding a colorful background and subtle sound effects could heighten the simulation’s atmosphere, making the school of boids feel more alive and enhancing the viewer’s experience.

Leave a Reply

Your email address will not be published. Required fields are marked *