Week 8: Follower Simulation

Concept

The idea for this assignment is to simulate the behavior of different living organisms that follow a certain leader. I just thought the idea of following something was interesting. In this case, the lead insect is wandering; however, the rest of the colony is seeking him.

Code

let leader;
let followers = [];
let numFollowers = 100;
let desiredSeparation = 50;  // Minimum distance between flies

function setup() {
  createCanvas(windowWidth, windowHeight);
  
  // Create leader fly with random starting position
  leader = new Vehicle(random(width), random(height), true);

  // Create follower flies
  for (let i = 0; i < numFollowers; i++) {
    followers.push(new Vehicle(random(width), random(height), false));
  }
}

function draw() {
  background(220, 30);  // Transparent background for trail effect
  
  // Leader wanders around
  leader.wander();
  leader.update();
  leader.checkEdges();  // Ensure leader stays in bounds
  leader.show();

  // Followers seek the leader and avoid overlapping
  for (let follower of followers) {
    follower.separate(followers);  // Avoid overlapping with other followers
    follower.seek(leader.position);  // Seek the leader's current position
    follower.update();
    follower.checkEdges();  // Ensure followers stay in bounds
    follower.show();
  }
}

// Vehicle class definition
class Vehicle {
  constructor(x, y, isLeader = false) {
    this.position = createVector(x, y);
    this.velocity = createVector(random(-1, 1), random(-1, 1));
    this.acceleration = createVector(0, 0);
    this.r = 6;
    this.maxspeed = isLeader ? 2 : 2.5;  // Leader moves slightly faster
    this.maxforce = 0.05;  // Reduce max force for smoother movements
    this.isLeader = isLeader;
    this.wanderTheta = 0;  // Used for wander behavior for leader
  }

  // Wander behavior for the leader
  wander() {
    let wanderR = 25;    // Radius for our "wander circle"
    let wanderD = 80;    // Distance for the wander circle to be ahead of the vehicle
    let change = 0.3;
    this.wanderTheta += random(-change, change);  // Randomly change wanderTheta

    // Now we have to calculate the new target
    let circleLoc = this.velocity.copy();
    circleLoc.setMag(wanderD);  // Move circle ahead of vehicle
    circleLoc.add(this.position);  // Make it relative to the vehicle's position

    let h = this.velocity.heading();  // Heading angle of the vehicle
    let circleOffset = createVector(wanderR * cos(this.wanderTheta + h), wanderR * sin(this.wanderTheta + h));

    let target = p5.Vector.add(circleLoc, circleOffset);
    this.seek(target);  // Seek the wandering target
  }

  // Method to update location
  update() {
    this.velocity.add(this.acceleration);
    this.velocity.limit(this.maxspeed);  // Limit speed
    this.position.add(this.velocity);
    this.acceleration.mult(0);  // Reset acceleration to 0 after each update
  }

  // A method that calculates a steering force towards a target
  seek(target) {
    let desired = p5.Vector.sub(target, this.position);  // A vector pointing from the location to the target
    desired.setMag(this.maxspeed);  // Scale to maximum speed

    let steer = p5.Vector.sub(desired, this.velocity);  // Steering = Desired minus velocity
    steer.limit(this.maxforce);  // Limit to maximum steering force

    this.applyForce(steer);
  }

  // Separation behavior: Avoid overlapping
  separate(vehicles) {
    let sum = createVector(0, 0);
    let count = 0;
    
    // Check all other vehicles
    for (let other of vehicles) {
      let d = p5.Vector.dist(this.position, other.position);
      if ((d > 0) && (d < desiredSeparation)) {
        // Calculate vector pointing away from neighbor
        let diff = p5.Vector.sub(this.position, other.position);
        diff.normalize();
        diff.div(d);  // Weight by distance
        sum.add(diff);
        count++;  // Keep track of how many are too close
      }
    }

    // Average out the forces
    if (count > 0) {
      sum.div(count);
      sum.setMag(this.maxspeed);  // Implement Reynolds: Steering = Desired - Velocity
      let steer = p5.Vector.sub(sum, this.velocity);
      steer.limit(this.maxforce);
      this.applyForce(steer);
    }
  }

  // Apply force to the vehicle
  applyForce(force) {
    this.acceleration.add(force);
  }

  // Check edges to ensure vehicle stays within the canvas
  checkEdges() {
    if (this.position.x > width) {
      this.position.x = width;
      this.velocity.x *= -1;
    } else if (this.position.x < 0) {
      this.position.x = 0;
      this.velocity.x *= -1;
    }
    
    if (this.position.y > height) {
      this.position.y = height;
      this.velocity.y *= -1;
    } else if (this.position.y < 0) {
      this.position.y = 0;
      this.velocity.y *= -1;
    }
  }

  // Display the vehicle as a triangle pointing in the direction of velocity
  show() {
    let angle = this.velocity.heading() + PI / 2;
    fill(this.isLeader ? color(255, 0, 0) : color(0, 255, 0));  // Leader is red, followers are green
    stroke(0);
    push();
    translate(this.position.x, this.position.y);
    rotate(angle);
    beginShape();
    vertex(0, -this.r * 2);
    vertex(-this.r, this.r * 2);
    vertex(this.r, this.r * 2);
    endShape(CLOSE);
    pop();
  }
}

The simulation uses a vehicle class that has different attributes including and is leader attribute which becomes true when the vehicle is a leader.  The class also has a seek-and-wander method. The wander method makes the vehicle wander around by creating a circle around the head of the vehicle and making the vehicle seek that circle.  The seek method takes a target and makes the vehicle seek the target object.

Reflection and Future Improvements

The mechanics of the motion look good however the sketch might need more styling and polishing.  Another thing would be making the leader controllable using a mouse.

 

Leave a Reply

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