Download the Wandering Agents App here – MeanderingAgents

 

A simple 2D Wandering Agent System built in Processing 3.0 inspired by Nature of Code. System also uses an image map, which when enabled acts as a multiplier for the Wander Change value.

Behavioral Classes
AppletObjectGUI
/////////////////////////////////////////////////////////////////////////////////////
import toxi.color.*;
import toxi.math.*;
import controlP5.*;
import java.util.*;
import toxi.geom.*;
//-------------------------------------------------------------------------------------------
int resetAmount = 0; 
float objectCount = 1000;
float sepVal, neighborDist, aligVal, cohVal, maxSpeed, maxSep, initSpeed,spawnEdge;
//-------------------------------------------------------------------------------------------
boolean valfromMap,showMap,SpawnEdge,simulate;
boolean setMap = false;
//-------------------------------------------------------------------------------------------
GUI cgui;
PImage img;
//-------------------------------------------------------------------------------------------
ArrayList group;
//-------------------------------------------------------------------------------------------
//---------------------------------------Settings--------------------------------------------
//-------------------------------------------------------------------------------------------
void settings() {
  size(1800, 1000, FX2D);
  smooth();
}
//---------------------------------------Setup-----------------------------------------------
//-------------------------------------------------------------------------------------------
void setup() {
  background(0);
  simulate = true;
  if (this.resetAmount == 0) {
    cgui = new GUI();
    cgui.run(this);
  }
  img = loadImage("MAP3.jpg");
  frameRate(100);
  this.group = new ArrayList();
  //-------------------------------------------------------------------------------------------
  for (int i = 0; i < objectCount; i ++) {
    Vec3D initVel, agLoc;
    if (!this.SpawnEdge) { // set init spawn and speed based on spawning on edge
      agLoc = new Vec3D(random(width), random(height), 0);
      initVel = new Vec3D(random(-initSpeed, initSpeed), random(-initSpeed, initSpeed), 0);
    } else { //spawn randomly and move randmly
      agLoc = new Vec3D(random(0), random(height), 0);
      initVel = new Vec3D(1, 0, 0);
    }
    Meanderer obj = new Meanderer(agLoc, initVel);
    group.add(obj);
  }
}
//---------------------------------------Draw------------------------------------------------
//-------------------------------------------------------------------------------------------
void draw() {
  background(0);
  //-------------------Set the field values for the meanderer--------------------------------
  this.initSpeed = this.cgui.is.getValue();
  this.objectCount = this.cgui.ac.getValue();

  for (int i = 0; i < this.group.size(); i++) {
    Meanderer obj = (Meanderer) this.group.get(i);
    obj.MaxForce = this.cgui.mf.getValue();
    obj.MaxSpeed = this.cgui.ms.getValue();
    obj.Multiplier = this.initSpeed;
    obj.randomize = this.cgui.rc.getState();
    obj.wandertheta = this.cgui.wt.getValue();
    obj.wanderR = this.cgui.wr.getValue();
    obj.wanderD = this.cgui.wd.getValue();
    obj.change = this.cgui.wc.getValue();
    obj.run();
  }
  if (showMap == true) {
    image(img, 0, 0);
    tint(255, 120);
  }
}
//---------------------------------------Keys------------------------------------------------
//-------------------------------------------------------------------------------------------
void keyPressed() {
  if (key == 'R') {
    this.resetAmount ++;
    setup();
  }
  if (key == 'U') {
    simulate = !simulate;
  }
  if (key == 'S') {
    showMap = !showMap;
  }
  if (key == 'A') {
    setMap = !setMap;
  }
    if (key == 'P') {
    simulate = !simulate;
    saveFrame("img-######.png");
  }
}
class Meanderer {
  Vec3D loc, vel;
  Vec3D acc = new Vec3D();
  float newMap, mapValue;
  float wandertheta, wanderR, wanderD, change;
  //----------------------------------------------------------------------
  float Multiplier, MaxSpeed, MaxForce;
  String tag = "A";
  int switchTrigger= 0;
  ArrayList<Vec3D> objectTrails = new ArrayList<Vec3D>();
  //----------------------------------------------------------------------
  boolean randomize = true;

  Meanderer(Vec3D location, Vec3D velBase) {
    this.loc = location;
    this.vel = velBase;
  }
  //----------------------------------------------------------------------
  //Run-------------------------------------------------------------------
  //Run all the other methods for this dude-------------------------------
  //----------------------------------------------------------------------
  void run() {
    wander();
    bordersRespawn();
    //bordersBounce();
    if (simulate) {
      move();
    }
    viz();
    setType();
    trail();
  }
  //----------------------------------------------------------------------
  //SetType---------------------------------------------------------------
  //Set the type based on its relationship to the image below-------------
  //----------------------------------------------------------------------
  void setType() {
    if (setMap) {
      if (this.loc.x <= img.width && this.loc.x >= 0 && this.loc.y >= 0 && this.loc.y <= img.height) {
        color c = img.pixels[floor(this.loc.y)*img.width+floor(this.loc.x)];
        int greyscale = round(red(c)*0.222+green(c)*0.707+blue(c)*0.071);
        this.newMap = map(greyscale, 0, 255, 0, 1);

        if (this.newMap > 0.35) {
          if (this.tag == "A" && this.switchTrigger != 0) {
            this.objectTrails = new ArrayList<Vec3D>();
          }
          this.tag = "B";
          this.switchTrigger ++;
        } else {
          this.tag = "A";
        }
      } else {
        this.newMap = 0;
      }
    }
  }
  //----------------------------------------------------------------------
  //Move------------------------------------------------------------------
  //Move and update the position------------------------------------------
  //----------------------------------------------------------------------
  void move() {
    this.vel.addSelf(this.acc);
    this.vel.normalize();
    this.vel.scaleSelf(this.Multiplier);
    this.vel.limit(this.MaxSpeed);
    this.loc.addSelf(this.vel);
    this.acc.clear();
  }
  //----------------------------------------------------------------------
  //Viz-------------------------------------------------------------------
  //Draw the head of the wanderer-----------------------------------------
  //----------------------------------------------------------------------
  void viz() {
    if (this.tag == "A") {
      stroke(255, 0, 0);
      strokeWeight(2);
    }
    if (this.tag == "B") {
      stroke(227, 255, 0);
      strokeWeight(3);
    }
    point(this.loc.x, this.loc.y);
  }
  //----------------------------------------------------------------------
  //Trail-----------------------------------------------------------------
  //The trail is drawn from previous loc to new location------------------
  //----------------------------------------------------------------------
  void trail() {
    this.objectTrails.add(new Vec3D(this.loc));
    if (this.objectTrails.size() > 0) {
      for (int j = 0; j < this.objectTrails.size()-1; j++) {
        Vec3D pos = this.objectTrails.get(j);
        Vec3D prevpos = this.objectTrails.get(j+1);
        if (this.tag == "A") {
          stroke(255, 0, 0, map(j, 0, this.objectTrails.size(), 0, 150));
          strokeWeight(map(j, 0, this.objectTrails.size(), 0.45, 1));
        } else if (this.tag == "B") {
          stroke(227, 255, 0, map(j, 0, this.objectTrails.size(), 0, 150));
          strokeWeight(map(j, 0, this.objectTrails.size(), 0.45, 0.75));
        }
        line(pos.x, pos.y, prevpos.x, prevpos.y);
      }
    }
  }
  //-----------------------------------------------------------------------
  //BorderRespawn----------------------------------------------------------
  //If we hit the boundary spawn at x depending on your spawn settings-----
  //-----------------------------------------------------------------------
  void bordersRespawn() {
    if (this.loc.x < 0 || this.loc.x > width || this.loc.y < 0 || this.loc.y > height) {
      if (SpawnEdge) {
        this.loc = new Vec3D(0, random(height), 0);
      } else {
        this.loc = new Vec3D(random(width), random(height), 0);
      }
      this.objectTrails = new ArrayList<Vec3D>();
    }
  }
  //----------------------------------------------------------------------
  //BorderBounce----------------------------------------------------------
  //Reverse the direction if we hit the boundary--------------------------
  //----------------------------------------------------------------------
  void bordersBounce() {
    if (this.loc.x > width) {
      this.vel.x = this.vel.x * -1;
    }
    if (this.loc.x < 0) {
      this.vel.x = this.vel.x * -1;
    }
    if (this.loc.y > height) {
      this.vel.y = this.vel.y * -1;
    }
    if (this.loc.y < 0) {
      this.vel.y = this.vel.y * -1;
    }
  }
  //--------------------------------------------------------------------------
  //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() {
    // wanderR = Radius for circle
    // wanderD = Distance for circle
    if (setMap) {
      if (this.loc.x <= img.width && this.loc.x >= 0 && this.loc.y >= 0 && this.loc.y <= img.height) {
        getColorValue();
        this.change *= this.newMap;
      } else {
        this.newMap = 0;
      }
    }
    if (this.randomize) {
      this.wandertheta += random(-this.change, this.change); // Randomly change wander theta
    } else {
      this.wandertheta += this.change;
    }
    Vec3D circleLoc = new Vec3D(this.vel.x, this.vel.y, 0);
    circleLoc.normalize(); // Normalize to get heading
    circleLoc.scaleSelf(this.wanderD);
    circleLoc.addSelf(this.loc);
    Vec3D circleOffSet = new Vec3D(this.wanderR*cos(wandertheta), this.wanderR*sin(wandertheta), 0);
    Vec3D target = circleLoc.addSelf(circleOffSet);
    Vec3D steer = target.sub(this.loc);
    steer.normalize();
    steer.scaleSelf(1);

    this.acc.addSelf(steer);
  }
  //----------------------------------------------------------------------
  //Snatch the color value from the image---------------------------------
  //----------------------------------------------------------------------
  void getColorValue() {
    color c = img.pixels[floor(this.loc.y)*img.width+floor(this.loc.x)];
    int greyscale = round(red(c)*0.222+green(c)*0.707+blue(c)*0.071);
    this.newMap = map(greyscale, 0, 255, 0, 1);
  }
}
class GUI {

  PApplet parent;
  ControlP5 cp5;
  int abc = 100;

  Slider wt, wr, wd, wc, is, ms, mf, ac;
  Toggle rc,se;

  public GUI () {
  }
  public void run(PApplet _parent) {
    parent = _parent;
    cp5 = new ControlP5(parent);
    cp5.setFont(createFont("Arial", 10));

    wt = cp5.addSlider("WanderTheta").plugTo(parent).setRange(0, 100).setPosition(10, 10).setValue(0.0).setDecimalPrecision(2).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
    wr = cp5.addSlider("WanderRadius").plugTo(parent).setRange(0.00, 100.00).setPosition(10, 22).setValue(65).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40)) ;
    wd = cp5.addSlider("WanderDistance").plugTo(parent).setRange(0.00, 500.00).setPosition(10, 34).setValue(60).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40)) ;
    wc = cp5.addSlider("WanderChange").plugTo(parent).plugTo(parent).setRange(0.00, 100.00).setPosition(10, 46).setValue(100).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40)) ;
    rc = cp5.addToggle("Randomize_Change").plugTo(parent).setPosition(10, 58).setSize(50, 10).setValue(true).setMode(ControlP5.SWITCH).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
    is = cp5.addSlider("InitSpeed").plugTo(parent).setRange(0.00, 10.00).setPosition(10, 90).setValue(2.0).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
    ms = cp5.addSlider("MaxSpeed").plugTo(parent).setRange(0.00, 10.00).setPosition(10, 102).setValue(2.0).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
    mf = cp5.addSlider("MaxForce").plugTo(parent).setRange(0.00, 10.00).setPosition(10, 114).setValue(0.10).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
    ac = cp5.addSlider("AgentCount").plugTo(parent).setRange(0.00, 3000.00).setPosition(10, 126).setValue(1000).setDecimalPrecision(3).setSize(50, 10).setHandleSize(10).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
    se = cp5.addToggle("SpawnEdge").plugTo(parent).setPosition(10, 138).setSize(50, 10).setValue(false).setMode(ControlP5.SWITCH).setColorForeground(color(255, 40)).setColorBackground(color(255, 40));
    //-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  }
  public void draw() {
    background(0);
  }
}

Controls
  • R – reset simulation
  • U – pause simulation
  • S – show/hide map
  • A – enable/disable map behavior
  • P – save screenshot