Processing: Autonomous Steering Behaviours – Part 02

networkswarm_0405

Swarm Behaviour – Population-Based Steering
To establish a life-like swarm behaviour we need to write some functions that calculate the steering forces described by Craig Reynolds. These functions will be called each iteration of the simulation and the results added to the acceleration of the agent at each time step. We shall break down each of these functions one-by-one, however it may be easier to move to the completed example beow to see how agent class is structured as an overall to get a better understanding.

 
  void update2Pop(ArrayList POP){
    // calculate steering forces based on population
    PVector sep = separate(POP);    // separate
    PVector ali = alignment(POP);     // alignment
    PVector coh = cohesion(POP);    // cohesion
 
    // arbitary weighting of these forces
    sep.mult(sepScale);
    ali.mult(aliScale);
    coh.mult(cohScale);
 
    // add the force vectors to acceleration
    acc.add(sep);
    acc.add(ali);
    acc.add(coh);
  }

agents

Separation
Separation applies a lateral steering force that results in the agent avoiding its neighbours, or if you like it effectively prevents overcrowding! The calculation is based on a weighted assessment of each agents neighbours, or population members who are within a given range (sight, smell, etc…). Generally we use the same range variable for all steering behaviours, however we decreaset he range of separation to ensure that cohesion can occur, before separation kicks in to stop excessive bunching.

 
  PVector separate(ArrayList POP){
    PVector sum = new PVector(0,0,0);
    int count = 0;
 
    for (int i = 0; i < POP.size(); i++){
      SuperAgent other = (SuperAgent) POP.get(i);          // get is a method of ArrayClass() in Java
      float distance = pos.dist(other.pos);                // use kVec.distance()
 
      // if distance > 0 and distance < range of vison (is in sight!?)
      if ((distance > 0) && (distance < rangeOfVision/3)){
        // calculate vector pointing away from neighbour
        PVector diff = pos.get();
        diff.sub(other.pos);
        diff.normalize();
        diff.mult(1/distance);                             // weight by distance
        sum.add(diff);                                     // add to create new position
        count++;
      }
    }
    // average -- divide by how many in range
    if (count > 0){
      sum.mult(1/(float)count);
    }
    return sum;
  }

agents

Alignment
Alignment is the averaging of all the headings (velocities) of an agents population within a given range (sight, smell, etc…). The result of alignment is that the population begins to flow in a similar manner, resulting in a more recognisable behavioural pattern.

 
  PVector align(ArrayList POP){
    PVector sum = new PVector(0,0,0);
    int count = 0;
 
    for (int i = 0; i < POP.size(); i++) {
      SuperAgent other = (SuperAgent) POP.get(i);
      float distance = pos.dist(other.pos);
      if ((distance > 0) && (distance < rangeOfVision)) {
        sum.add(other.vel);
        count++;
      }
    }
    if (count > 0){
      sum.mult(1/(float)count);
      sum.limit(maxForce);
    }
    return sum;
  }

agents

Cohesion
Cohesion is a steering force toward the average position of all the population members within range (sight, smell, etc…).

 
  PVector cohesion(ArrayList POP){
    PVector sum = new PVector(0,0,0);
    int count = 0;
 
    for (int i = 0; i < POP.size(); i++){
      SuperAgent other = (SuperAgent) POP.get(i);          // get is a method of ArrayClass() in Java
      float distance = pos.dist(other.pos);        // use kVec.distance()
      if ((distance > 0) && (distance < rangeOfVision)){
        sum.add(other.pos);                     // add to create new position
        count++;
      }
    }
    if (count > 0){
      sum.mult(1/(float)count);
      return steer(sum,rangeOfVision, true);
    }
    return sum;
  }

dumbSwarm

Example 01: A simple 2D swarm

 
/*-------------------------------------------------------------------------------------------------
 Declare our variables
 -------------------------------------------------------------------------------------------------*/
ArrayList agents;       // an arraylist to store all of our agents!
PVector TARGET;         // The target will be the current position of the mouse!
 
int numAgents = 25;
int ENVX      = 600;    // size of the environment in the X direction 
int ENVY      = 250;    // size of the environment in the Y direction
 
/*-------------------------------------------------------------------------------------------------
 Setup Sketch
 -------------------------------------------------------------------------------------------------*/
void setup(){
 
  size(ENVX,ENVY, P3D);                            // set the applet size to desired environment
  background(0);                                   // set the applet background to black
  ellipseMode(CENTER);
 
  agents = new ArrayList();                        // make our arraylist to store our agents
 
  TARGET = new PVector(ENVX/2, ENVY/2, 0);      // make a starting target
 
 
  // loop to make our agents!
  for(int i = 0; i < numAgents; i++){
    agents.add( new SuperAgent() );                   
  }
 
}
 
/*-------------------------------------------------------------------------------------------------
 Draw Loop
 -------------------------------------------------------------------------------------------------*/
void draw(){
  background(0);                                  // Set the applet background to black
 
  TARGET = new PVector(mouseX, mouseY, 0);        // if mouse is pressed then update target
 
  for(int i = 0; i < agents.size(); i++){
    SuperAgent A = (SuperAgent) agents.get(i);    
    A.run(agents);                                // Pass the population of agents to the agent!
  }
 
  if(keyPressed) saveFrame("swarm_###.jpg"); // screen capture
 
} 
 
/*-------------------------------------------------------------------------------------------------
 Agent Class
 -------------------------------------------------------------------------------------------------*/
class SuperAgent{
 
  // *** CLASS VARIABLES ***
  PVector pos, vel, acc;
  float maxVel, maxForce, rangeOfVision, cohScale, aliScale, sepScale;
  int agentSize, agentColour;
 
  // *** CLASS CONSTRUCTOR ***
  SuperAgent(){
    // 3 vectors to describe agents position, heading & speed
    pos           = new PVector( random(0,width), random(0,height), 0 );
    vel           = new PVector( random(-1,1), random(-1,1), 0 );
    acc           = new PVector(0,0,0);
    // max speed and steering force variables
    maxVel        = random(1.5, 3.0);
    maxForce      = random(0.2, 0.5);  
    // steering force variables
    cohScale      = 1.0;
    aliScale      = 1.0;
    sepScale      = 16.0;    
    rangeOfVision = 50.0;
    // Appearance of agent
    agentSize     = 10;
    agentColour   = 175;
  }
 
  // *** CLASS METHODS ***
 
  //--------------------------------------------------------PRIMARY FUNCTION - call all other functions here!
  void run(ArrayList POP){ 
 
    // update population based steering
    updateSteering(POP);   
 
    // update position
    vel.add(acc);         // add the acceleration to the velocity
    vel.limit(maxVel);    // clip the velocity to a maximum allowable 
    pos.add(vel);         // add velocity to position
    acc.set(0,0,0);       // make sure we set acceleration back to zero!
 
    // standard update functions
    toroidalBorders();    // wrap around screen - agent leaves on the left and returns on the right!
    render();             // draw the agent!
  }
 
  //--------------------------------------------------------UPDATE STEERING
  void updateSteering(ArrayList POP){
    // calculate the steering forces
    PVector coh = cohesion(POP);
    PVector sep = separation(POP);
    PVector ali = alignment(POP);
    // scale the steering forces
    coh.mult(cohScale);
    sep.mult(sepScale);
    ali.mult(aliScale);
    // add to acceleration
    acc.add(coh);
    acc.add(sep);
    acc.add(ali);
  }
 
  //--------------------------------------------------------WRAP AROUND SCREEN - KEEP AGENTS ON THE SCREEN
  void toroidalBorders(){
    if(pos.x < 0   ) pos.x = ENVX;
    if(pos.x > ENVX) pos.x = 0;
    if(pos.y < 0   ) pos.y = ENVY;
    if(pos.y > ENVY) pos.y = 0;
  }  
 
  //--------------------------------------------------------DRAW THE AGENT
  void render(){
    stroke(agentColour);
    fill(agentColour,90);
    // put a circle on the agents position
    ellipse(pos.x,pos.y, agentSize, agentSize);
    // draw the heading of the agent
    line(pos.x, pos.y, pos.x+(vel.x*agentSize), pos.y+(vel.y*agentSize) );    
  }
 
  // *** STEERING BEHAVIOURS ***
 
  //--------------------------------------------------------SEEK TARGET
  void seek(PVector target, float threshold, boolean slowDown){
    acc.add( steer(target,threshold, slowDown) );
  }
 
  //--------------------------------------------------------STEER
  PVector steer (PVector target, float threshold, boolean slowDown ){
    PVector steerForce;  // The steering vector
    target.sub(pos);
    float d2 = target.mag();
 
    if ( d2 > 0 && d2 < threshold){
      target.normalize();
      if( (slowDown) && d2 < threshold/2 ) target.mult( maxVel * (threshold/ENVX) );
      else target.mult(maxVel);
      target.sub(vel);
      steerForce = target.get();
      steerForce.limit(maxForce);
    }
    else {
      steerForce = new PVector(0,0,0);
    }
    return steerForce;
  }
 
  //--------------------------------------------------------SEPARATE
  PVector separation(ArrayList POP){
    PVector sum = new PVector(0,0,0);
    int count = 0;
 
    for (int i = 0; i < POP.size(); i++){
      SuperAgent other = (SuperAgent) POP.get(i);          // get is a method of ArrayClass() in Java
      float distance = pos.dist(other.pos);                // use kVec.distance()
 
      // if distance > 0 and distance < range of vison (is in sight!?)
      if ((distance > 0) && (distance < rangeOfVision/3)){
        // calculate vector pointing away from neighbour
        PVector diff = pos.get();
        diff.sub(other.pos);
        diff.normalize();
        diff.mult(1/distance);                             // weight by distance
        sum.add(diff);                                     // add to create new position
        count++;
      }
    }
    // average -- divide by how many in range
    if (count > 0){
      sum.mult(1/(float)count);
    }
    return sum;
  }
 
  //--------------------------------------------------------ALIGNMENT 
  PVector alignment(ArrayList POP){
    PVector sum = new PVector(0,0,0);
    int count = 0;
 
    for (int i = 0; i < POP.size(); i++) {
      SuperAgent other = (SuperAgent) POP.get(i);
      float distance = pos.dist(other.pos);
      if ((distance > 0) && (distance < rangeOfVision)) {
        sum.add(other.vel);
        count++;
      }
    }
    if (count > 0){
      sum.mult(1/(float)count);
      sum.limit(maxForce);
    }
    return sum;
  }
 
  //--------------------------------------------------------COHESION
  PVector cohesion(ArrayList POP){
    PVector sum = new PVector(0,0,0);
    int count = 0;
 
    for (int i = 0; i < POP.size(); i++){
      SuperAgent other = (SuperAgent) POP.get(i);          // get is a method of ArrayClass() in Java
      float distance = pos.dist(other.pos);        // use kVec.distance()
      if ((distance > 0) && (distance < rangeOfVision)){
        sum.add(other.pos);                     // add to create new position
        count++;
      }
    }
    if (count > 0){
      sum.mult(1/(float)count);
      return steer(sum,rangeOfVision, true);
    }
    return sum;
  }
 
}

About this entry