logo

[2]D BabyMaking Trackers

Projects details

Download the Tracking Agents App here – Tracking Agents

A simple 2D MultiAgent Path Following System built in Processing 3.0 inspired by Nature of Code and Silkworm Behaviors. “Parent” objects have the capability to spawn “Children” once inside the path radius.

 

PROCESSING 3.0 SKETCH CODE
MAIN
import java.util.List;
import toxi.geom.*;
import controlP5.*;
//-------------------------------------------------------------------------------------------
Tracker a;
ControlP5 cp5;
GUI cgui;
Path tempPath;

Vec3D locStart;
Vec3D triggerLoc = new Vec3D(0, 0, 0);

boolean simulate;
boolean diagram,drawFutureLoc,addNew;
boolean drawPaths = true;
boolean spawnEdge = true;
int resetAmount = 0; 
int agentCount = 100;
int triggerCount = 0;
int pathCount = 10;
float stepCount = 0;

List<Tracker> agentList;
ArrayList<Vec3D>childSpawners;
ArrayList childSpawnType;
ArrayList<Path> pathList;
//-------------------------------------------------------------------------------------------
//---------------------------------------Settings--------------------------------------------
//-------------------------------------------------------------------------------------------
void settings() {
  size(1800, 1000, FX2D);
  smooth();
}
//-------------------------------------------------------------------------------------------
//---------------------------------------Setup-----------------------------------------------
//-------------------------------------------------------------------------------------------
void setup() {
  background(0);

  this.agentList = new ArrayList<Tracker>();
  this.childSpawners = new ArrayList<Vec3D>();
  this.childSpawnType = new ArrayList();
  this.pathList = new ArrayList<Path>();
  this.simulate = true;

  if (this.resetAmount == 0) {
    this.cgui = new GUI();
    this.cgui.run(this);
  }
  for (int pth = 0; pth < this.pathCount; pth++) {
    newPath();
  }
  for (int i = 0; i < this.agentCount; i ++) {
    Vec3D speed;
    if (this.spawnEdge) {
      this.locStart = new Vec3D(0, random(height), 0);
      speed = new Vec3D(1, 0, 0);
    } else {
      this.locStart = new Vec3D(random(width), random(height), 0);
      speed = new Vec3D(random(-1, 1), random(-1, 1), 0);
    }
    this.a = new Tracker(this.locStart, speed, true, "parent", "main");
    this.agentList.add(this.a);
  }
}
//-------------------------------------------------------------------------------------------
//---------------------------------------Draw------------------------------------------------
//-------------------------------------------------------------------------------------------
void draw() {
  background(0);

  this.spawnEdge = this.cgui.t0.getState();
  this.agentCount = (int)this.cgui.s7.getValue();
  for (Path pths : pathList) {
    if (drawPaths) {
      pths.display();
    }
    pths.radius = this.cgui.s0.getValue();
  }

  for (Tracker ag : this.agentList) {
    ag.wanderD = this.cgui.sw1.getValue();
    ag.wanderR = this.cgui.s1.getValue();
    ag.wanderT = this.cgui.sw3.getValue();
    ag.wanderTVal = this.cgui.sw2.getValue();

    ag.maxforce = this.cgui.s6.getValue();
    ag.max = this.cgui.s5.getValue();
    ag.amp = this.cgui.s9.getValue();
    ag.vel = this.cgui.s4.getValue();
    ag.maxDist = this.cgui.s2.getValue();
    ag.r = this.cgui.s3.getValue();
    ag.maxChildren = (int)this.cgui.s8.getValue();
    ag.run();
  }

  if (this.childSpawners.size() > 0) {
    newDude();
    this.childSpawners = new ArrayList<Vec3D>();
    this.childSpawnType = new ArrayList();
  }
  this.stepCount++;
}
//-------------------------------------------------------------------------------------------
//---------------------------------------Create New Path-------------------------------------
//-------------------------------------------------------------------------------------------
void newPath() {
  this.tempPath = new Path();
  this.tempPath.addPoint(random(30, 300), random(height/4, height));
  this.tempPath.addPoint(random(101, width/2), random(0, height));
  this.tempPath.addPoint(random(width/2, width), random(0, height));
  this.tempPath.addPoint(random(width-100, width), height/2);
  this.pathList.add(this.tempPath);
}
//-------------------------------------------------------------------------------------------
//---------------------------------------Keys------------------------------------------------
//-------------------------------------------------------------------------------------------
void keyPressed() {
  if (key == 'R') {
    this.resetAmount ++;
    this.triggerCount = 0;
    setup();
  }
  if (key == 'S') this.simulate = !this.simulate;
  if (key == 'W') newPath();
  if (key == 'Q') this.drawFutureLoc = !this.drawFutureLoc;
  if (key == 'Q') this.diagram = !this.diagram;
  if (key == 'P') this.drawPaths = !this.drawPaths;
  if (key == 'I') saveFrame("img-######.png");
}
//-------------------------------------------------------------------------------------------
//----------------------------------BabyMakers-----------------------------------------------
//-------------------------------------------------------------------------------------------
void newDude() {
  int babyCount = 0;
  for (Vec3D px : this.childSpawners) {
    Vec3D speed;
    if (this.spawnEdge) {
      speed = new Vec3D(1, 0, 0);
    } else {
      speed = new Vec3D(random(-1, 1), random(-1, 1), 0);
    }
    if ((int)this.childSpawnType.get(babyCount) % 2 == 0) {
      this.agentList.add(new Tracker(new Vec3D(px), speed, false, "child", "w_a"));
    } else {
      this.agentList.add(new Tracker(new Vec3D(px), speed, false, "child", "w_b"));
    }
    babyCount++;
  }
}
Update
OBJECT
class Tracker {
  List<Vec3D> objectTrails;
  
  Vec3D speed;
  Vec3D acc = new Vec3D(0, 0, 0);
  Vec3D loc;
  
  String type, babyType;
  
  float maxforce = 0.01;
  float max = 4.0f;
  float vel = 1.0;
  float amp,r;
  float maxDist = 100;

  boolean sepActive = false;
  boolean wonderTrigger = false;
  boolean instanceable;

  int maxChildren,instanceTriggerCount;

  float wandertheta;
  float wanderTVal; //value to swap rotation after x position changes
  float wanderD; // Distance for our "wander circle"
  float wanderR; //Radius for our "wander circle"
  float wanderT; // Randomly change wander theta
  //-------------------------------------------------------------------------------------
  Tracker(Vec3D location, Vec3D Speed, boolean instance, String Type, String BabyType) {
    this.loc = location;
    this.objectTrails = new ArrayList<Vec3D>();
    this.r = 12;
    this.instanceTriggerCount = 0;
    this.instanceable = instance;
    this.type = Type;
    this.wandertheta = 0;
    this.babyType = BabyType;
    this.speed = Speed;
  }
  //----------------------------------------------------------------------
  //Run-------------------------------------------------------------------
  //Run all the other methods for this dude-------------------------------
  //----------------------------------------------------------------------
  void run() {
    pathFollow();
    if (this.sepActive && this.instanceable) {
     separate();
     }
     if(this.wonderTrigger){
     wander();
     } 
     /*
    if (this.wonderTrigger) {
      wander();
    }
    */
    if (simulate) {
      move();
    }
    viz();
    trail();
  }
  //--------------------------------------------------------------------------
  //Wanderer Behavior---------------------------------------------------------
  //“Wandering is a type of random steering which has some long term order: -
  //the steering direction on one frame is related to the steering direction 
  //on the next frame. This produces more interesting motion than, for example
  //,simply generating a random steering direction each frame.” Reynolds-----
  //--------------------------------------------------------------------------
  void wander() {   
    if (stepCount < this.wanderTVal) {
      if (type == "parent") {
        this.wandertheta = this.wanderT;
      } else {
        if (babyType == "w_a") {
          this.wandertheta = this.wanderT;
        } else {
          this.wandertheta = -this.wanderT;
        }
      }
    } else if (stepCount >= this.wanderTVal && stepCount <this.wanderTVal*2) {
      if (type == "parent") {
        this.wandertheta = -this.wanderT;
      } else {
        if (babyType == "w_a") {
          this.wandertheta = -this.wanderT;
        } else {
          this.wandertheta = this.wanderT;
        }
      }
    } else {
      stepCount = 0;
    }
    // Now we have to calculate the new location to steer towards on the wander circle
    Vec3D circleloc = speed.copy();    // Start with velocity
    circleloc.normalize();            // Normalize to get heading
    circleloc.scaleSelf(this.wanderD);          // Multiply by distance
    circleloc.addSelf(this.loc);               // Make it relative to boid's location
    float h = speed.headingXY();        // We need to know the heading to offset wandertheta
    Vec3D circleOffSet = new Vec3D(this.wanderR*cos(this.wandertheta+h), this.wanderR*sin(this.wandertheta+h), 0);
    Vec3D target = circleloc.add(circleOffSet);
    seek(target);
    
    if (diagram) drawWanderStuff(loc, circleloc, target, wanderR);  // Render wandering circle, etc. 
  }  
  //-------------------------------------------------------------------------------------------
  //Diagram------------------------------------------------------------------------------------
  //A method just to draw the circle associated with wandering---------------------------------
  //-------------------------------------------------------------------------------------------
  void drawWanderStuff(Vec3D location, Vec3D circle, Vec3D target, float rad) {
    stroke(255); 
    noFill();
    ellipseMode(CENTER);
    ellipse(circle.x, circle.y, rad*2, rad*2);
    ellipse(target.x, target.y, 4, 4);
    line(location.x, location.y, circle.x, circle.y);
    line(circle.x, circle.y, target.x, target.y);
  }
  //-------------------------------------------------------------------------------------------
  //Path Follow Method-------------------------------------------------------------------------
  //Follows the path if inside the radius------------------------------------------------------
  //-------------------------------------------------------------------------------------------
  void pathFollow() {
    Vec3D predict = this.speed.copy();
    predict.normalize();
    predict.scaleSelf(this.amp);
    Vec3D nextPosPrev = loc.add(predict);
    Vec3D target = null;
    Vec3D normal = null;
    float worldRecord = 1000000;
    for (int z = 0; z < pathList.size(); z++) {
      for (int i = 0; i <  pathList.get(z).points.size()-1; i++) { 
        Vec3D a = pathList.get(z).points.get(i);
        Vec3D b = pathList.get(z).points.get(i+1);
        Vec3D normalPoint = getNormalPoint(nextPosPrev, a, b); //Finding the normals for each line segment
        if (normalPoint.x < min(a.x, b.x) || normalPoint.x > max(a.x, b.x)) {
          normalPoint = b.copy();
        }
        float distance = nextPosPrev.distanceTo(normalPoint);
        if (distance < worldRecord) {
          worldRecord = distance;
          normal = normalPoint;
          Vec3D dir = b.sub(a); //Look at the direction of the line segment so we can seek a little bit ahead of the normal
          dir.normalize();
          dir.scaleSelf(10);
          target = normalPoint.copy();
          target.add(dir);
        }
      }
    }
    //remove if you want to just go to the line
    //maxDist = 10000000000000f;
    if (worldRecord < this.maxDist) {
      this.sepActive = true;
      this.wonderTrigger = true;
      if (worldRecord > tempPath.radius) {
        seek(target);
      } else {
        //IF TYPE IS PARENT AND HAS CREATED LESS THAN THE ALLOWED CHILDREN THEN CREATE MORE
        if (this.instanceable && instanceTriggerCount < maxChildren) { 
          addNew = true;
          triggerLoc = nextPosPrev.copy();
          childSpawners.add(new Vec3D(this.loc));
          childSpawnType.add(instanceTriggerCount);
          triggerCount++;        
          instanceTriggerCount ++;
        } else {
          addNew = false;
        }
        Vec3D zero = new Vec3D(0, 0, 0);
        zero.scaleSelf(3);
        acc.addSelf(zero);
      }
    } else {
      wonderTrigger = false;
    }
    // Draw the debugging stuff
    if (drawFutureLoc) {
      // Draw predicted future location
      stroke(255);
      fill(0);
      line(this.loc.x, this.loc.y, this.loc.x, this.loc.y);
      ellipse(nextPosPrev.x, nextPosPrev.y, 4, 4);
      // Draw normal location
      stroke(255);
      fill(0);
      ellipse(normal.x, normal.y, 4, 4);
      // Draw actual target (red if steering towards it)
      line(nextPosPrev.x, nextPosPrev.y, normal.x, normal.y);
      if (worldRecord > tempPath.radius) fill(255, 0, 0);
      noStroke();
      ellipse(target.x, target.y, 8, 8);
    }
  }
  //-------------------------------------------------------------------------------------------
  //-------------------Get the Normal Point on the Path Method---------------------------------
  //-------------------------------------------------------------------------------------------
  Vec3D getNormalPoint(Vec3D p, Vec3D a, Vec3D b) {
    Vec3D ap = p.sub(a);
    Vec3D ab = b.sub(a);
    ab.normalize();
    ab.scaleSelf(ap.dot(ab)); //Using the dot product for scalar projection
    Vec3D normalPoint = a.add(ab); // Finding the normal point along the line segment
    return normalPoint;
  }
  //-------------------------------------------------------------------------------------------
  //---------------------------------------Seek Target Method----------------------------------
  //-------------------------------------------------------------------------------------------
  void seek(Vec3D target) {
    Vec3D desired = target.sub(this.loc);
    desired.normalize();
    desired.scaleSelf(this.max);
    Vec3D steer = desired.sub(this.speed);
    steer.limit(this.maxforce); // Limit the magnitude of the steering force.
    steer.scaleSelf(3);
    applyForce(steer);
  }
  //-------------------------------------------------------------------------------------------
  //Apply Force Method-------------------------------------------------------------------------
  //-------------------------------------------------------------------------------------------
  void applyForce(Vec3D force) {
    this.acc.addSelf(force);
  }
  //----------------------------------------------------------------------
  //Move------------------------------------------------------------------
  //Move and update the position------------------------------------------
  //----------------------------------------------------------------------
  void move() {
    this.speed.addSelf(this.acc);
    this.speed.limit(this.max);
    this.loc.addSelf(this.speed);
    this.objectTrails.add(new Vec3D(this.loc));
    this.acc.clear();
  }
  //-------------------------------------------------------------------------------------------
  //Viz Method---------------------------------------------------------------------------------
  //Draw heads---------------------------------------------------------------------------------
  void viz() {
    if (type == "parent") {
      stroke(255, 0, 0);
      strokeWeight(6);
      point(this.loc.x, this.loc.y);
    } else {
      if (babyType == "w_a") {
        stroke(0, 0, 255);
        strokeWeight(6);
        point(this.loc.x, this.loc.y);
      } else {
        stroke(0, 255, 255);
        strokeWeight(6);
        point(this.loc.x, this.loc.y);
      }
    }
  }
  //-------------------------------------------------------------------------------------------
  //Separation Method--------------------------------------------------------------------------
  //Method checks for nearby boids and steers away---------------------------------------------
  //-------------------------------------------------------------------------------------------
  void separate () {
    float desiredseparation = this.r*2;
    Vec3D steer = new Vec3D(0, 0, 0);
    int count = 0;
    // For every boid in the system, check if it's too close
    for (int i = 0; i < agentList.size(); i++) {
      Tracker other = (Tracker) agentList.get(i);
      float d = this.loc.distanceTo(other.loc);
      // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
      if ((d > 0) && (d < desiredseparation)) {
        // Calculate vector pointing away from neighbor
        Vec3D diff = loc.sub(other.loc);
        diff.normalize();
        diff.normalizeTo(1/d);        // Weight by distance
        steer.addSelf(diff);
        count++;            // Keep track of how many
      }
    }
    // Average -- divide by how many
    if (count > 0) {
      steer.scaleSelf(1.0/(float)count);
    }
    // As long as the vector is greater than 0
    if (steer.magnitude() > 0) {
      // Implement Reynolds: Steering = Desired - Velocity
      steer.normalize();
      steer.scaleSelf(this.max);
      steer.subSelf(this.speed);
      steer.limit(this.maxforce);
    }
    steer.scaleSelf(3);
    applyForce(steer);
  }
  //-------------------------------------------------------------------------------------------
  //Trail Method-------------------------------------------------------------------------------
  //The trail is drawn from previous loc to new location---------------------------------------
  //-------------------------------------------------------------------------------------------
  void trail() {

    if (this.objectTrails.size() > 0) {
      for (int j = 0; j < this.objectTrails.size(); j++) {
        if (j != 0) {
          Vec3D pos = this.objectTrails.get(j);
          Vec3D prevpos = this.objectTrails.get(j - 1);
          if(this.type == "parent"){
            stroke(255, 0, 0, map(j, 0, this.objectTrails.size(), 0, 200));
            strokeWeight(map(j, 0, this.objectTrails.size(), 0.45, 1.5));
          }else if(this.babyType == "w_a"){
            stroke(0, 0, 255, map(j, 0, this.objectTrails.size(), 0, 200));
            strokeWeight(map(j, 0, this.objectTrails.size(), 0.45, 1.0));
          }else{
            stroke(0,255, 255, map(j, 0, this.objectTrails.size(), 0, 200));
            strokeWeight(map(j, 0, this.objectTrails.size(), 0.45, 1.0));
          }       
          line(pos.x, pos.y, prevpos.x, prevpos.y);
        }
      }
    }
  }
}
Update
UI
class GUI {

  PApplet parent;
  ControlP5 cp5;
  int abc = 100;

  Slider s0, s1, s2, s3, s4, s5, s6, s7, s8, s9,sw1,sw2,sw3;
  Toggle t0;

  public GUI () {
  }

  public void run(PApplet _parent) {
    parent = _parent;
    cp5 = new ControlP5(parent);
    this.cp5.setFont(createFont("Arial", 10));
    
    this.s0 = cp5.addSlider("PathRad").plugTo(parent).setRange(0, 100).setPosition(10, 10).setValue(28.0).setDecimalPrecision(2).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
    this.s9 = cp5.addSlider("ScalarProjectDist").plugTo(parent).plugTo(parent).setRange(0.00, 100.00).setPosition(10, 22).setValue(25).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40)) ;
    this.s2 = cp5.addSlider("PathTresh").plugTo(parent).setRange(0.00, 500.00).setPosition(10, 34).setValue(500.0).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40)) ;

    this.s1 = cp5.addSlider("WanderRadius").plugTo(parent).setRange(0.00, 500.00).setPosition(10, 46).setValue(10).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40)) ;
    this.sw1 = cp5.addSlider("WanderDist").plugTo(parent).setRange(0.00, 100.00).setPosition(10, 58).setValue(20).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40)) ;
    this.sw2 = cp5.addSlider("WanderRotTrigger").plugTo(parent).setRange(0.00, 100.00).setPosition(10, 70).setValue(6).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40)) ;
    this.sw3 = cp5.addSlider("WanderTheta").plugTo(parent).setRange(0.0, 3.00).setPosition(10, 82).setValue(0.5).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40)) ;

    this.s8 = cp5.addSlider("MaxChildren").plugTo(parent).plugTo(parent).setRange(0.00, 20.00).setPosition(10, 94).setValue(0).setDecimalPrecision(0).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40)) ;

    this.s4 = cp5.addSlider("InitSpeed").plugTo(parent).setRange(0.00, 10.00).setPosition(10, 106).setValue(2.0).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
    this.s5 = cp5.addSlider("MaxSpeed").plugTo(parent).setRange(0.00, 10.00).setPosition(10, 118).setValue(3.0).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
    this.s6 = cp5.addSlider("MaxForce").plugTo(parent).setRange(0.00, 2.00).setPosition(10, 130).setValue(0.16).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
    this.s3 = cp5.addSlider("MaxSep").plugTo(parent).plugTo(parent).setRange(0.00, 20.00).setPosition(10, 142).setValue(3.4).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40)) ;
    
    this.s7 = cp5.addSlider("AgentCount").plugTo(parent).setRange(0.00, 1000.00).setPosition(10, 154).setValue(100).setDecimalPrecision(0).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
    this.t0 = cp5.addToggle("SpawnEdge").plugTo(parent).setPosition(10, 166).setSize(50, 10).setValue(true).setMode(ControlP5.SWITCH).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
  }

  public void draw() {
    background(0);
  }
}
Update
PATH
class Path {
  ArrayList<Vec3D> points;
  float radius = 20;
  
  Path() {
    points = new ArrayList<Vec3D>();
  }
  void addPoint(float x, float y) {   
    Vec3D point = new Vec3D(x, y, 0);
    points.add(point);
  }
  //-------------------------------------------------------------------------------------------
  //---------------------------------------Draw the Path---------------------------------------
  //-------------------------------------------------------------------------------------------
  void display() {
    // Draw thick line for radius
    stroke(175,0,0,50);
    strokeWeight(radius*2);
    noFill();
    beginShape();
    for (Vec3D v : points) {
      vertex(v.x, v.y);
    }
    endShape();
    // Draw thin line for center of path
    stroke(255);
    strokeWeight(1);
    noFill();
    beginShape();
    for (Vec3D v : points) {
      vertex(v.x, v.y);
    }
    endShape();
  }
}
Update
Controls
  • I – save screenshot
  • P – show/hide pathsy
  • Q – show/hide move diagrams
  • W – add a new path to the path list
  • S – pause/resume simulation
  • R – reset simulation

 

 

 

 

  • Share

Leave a reply

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