hardmaru_creatures/creatures_v1.pde
// extended and modified from dan shiffman's natureofcode examples
 
// A list of creatures
ArrayList<Creature> creatures;
ArrayList<Wall> walls;
 
float wallx1, wallx2, wally1, wally2;
boolean creatingWall = false;
boolean wallBuildingMode = false;
int gridSnap = 32;
PFont myFont;
 
int PREDATOR = 1;
int PREY = 2;
 
float predator_radius = 16;
float prey_radius = 5;
 
float LARGENUM = 1000000000000000.0;
 
void setup() {
  size(800, 600);
 
  myFont = createFont("Courier New", 12);
  textFont(myFont);
 
 
  // We are now making random creatures and storing them in an ArrayList
  creatures = new ArrayList<Creature>();
  walls = new ArrayList<Wall>();
 
  // We are now making random creatures and storing them in an ArrayList
  creatures = new ArrayList<Creature>();
  walls = new ArrayList<Wall>();
 
  // big creature
  for (int i = 0; i < 16; i++) {
    creatures.add(new Creature(random(gridSnap, width - gridSnap), random(gridSnap, height-gridSnap), PREDATOR, predator_radius) );
  }
 
  // prey creature
  for (int i = 0; i < 16; i++) {
    addRandomPrey();
  }
 
  //walls.add( new Wall(gridSnap, gridSnap, width-gridSnap, gridSnap) );
  //walls.add( new Wall(width-gridSnap, gridSnap, width-gridSnap, height-gridSnap) );
  //walls.add( new Wall(width-gridSnap, height-gridSnap, gridSnap, height-gridSnap) );
  //walls.add( new Wall(gridSnap, height-gridSnap, gridSnap, gridSnap) );
  walls.add( new Wall(width/2, 100+gridSnap, width/2, height-100-gridSnap) );
  walls.add( new Wall(0, height/2, width/2-100-gridSnap, height/2) );
  walls.add( new Wall(width/2, height/3, width-100-gridSnap, height/3) );
  walls.add( new Wall(width/2+100+gridSnap, 2*height/3, width, 2*height/3) );
  walls.add( new Wall(100, 100, width/2-100, height/2-100) );
  walls.add( new Wall(100, height-100, width/2-100, height/2+100) );
}
 
void draw() {
  background(255);
 
  // spaceball resets
  if (keyPressed) {
    if (key == 'r') {
      setup();
    }
    if (key == 'w') {
      wallBuildingMode = true;
    }
    if (key == 'f') {
      wallBuildingMode = false;
      creatingWall = false;
    }
  }
 
  // draw grid snap cross
  if ( wallBuildingMode == true ) {
    stroke(0);
    float snapX = round(mouseX/gridSnap)*gridSnap;
    float snapY = round(mouseY/gridSnap)*gridSnap;
    line(snapX-gridSnap/2, snapY, snapX+gridSnap/2, snapY);
    line(snapX, snapY-gridSnap/2, snapX, snapY+gridSnap/2);
  }
 
  // Instructions
  fill(0);
  text("Click to place food.  'w'/'f' to toggle wall building / feeding.  'r' to reset.", 10, height-gridSnap/4);
 
  Creature c;
  for (int i = 0; i < creatures.size (); i++) {
    c = creatures.get(i);
    // Path following and separation are worked on in this function
    c.applyBehaviors(creatures, walls);
    // Call the generic run method (update, borders, display, etc.)
    c.update();
    c.display();
    // if creature falls outside then kill it
 /*   PVector theLoc = c.getLoc();
    if ( (theLoc.x < gridSnap) || (theLoc.x > (width-gridSnap)) || (theLoc.y < gridSnap) || (theLoc.y > (height-gridSnap)) ) {
      creatures.remove(i);
    }*/
  }
 
  for (Wall w : walls) {
    w.display();
  }
 
  if (creatingWall == true) {
    line(wallx1, wally1, mouseX, mouseY);
  }
 
 
  if (mousePressed) {
    if (wallBuildingMode == false) {
      addTarget(mouseX, mouseY);
    } else {
      if (creatingWall == false) {
        creatingWall = true;
        wallx1 = mouseX;
        wally1 = mouseY;
      } else {
        wallx2 = mouseX;
        wally2 = mouseY;
        if ( (abs(wally2-wally1) > gridSnap) || (abs(wallx2-wallx1) > gridSnap) ) {
          creatingWall = false;
          walls.add( new Wall(wallx1, wally1, wallx2, wally2) );
        }
      }
    }
  }
}
 
// extended and modified from dan shiffman's natureofcode examples
 
void addRandomPrey() {
  creatures.add(new Creature(random(gridSnap, width - gridSnap), random(gridSnap, height-gridSnap), PREY, prey_radius) );
}
 
void addTarget(float x, float y) {
  if ( detectTarget(x, y) == false ) { // dont put target on top of another target
    creatures.add(new Creature(x, y, PREY, prey_radius) );
  }
}
 
 
boolean detectTarget(float x, float y) {
  float desiredseparation, xd, yd;
  PVector theTarget;
  // For every boid in the system, check if it's too close
  for (int i = 0; i < creatures.size (); i++) {
    theTarget = creatures.get(i).getLoc();
    desiredseparation = prey_radius*prey_radius;
    xd = (x - theTarget.x);
    yd = (y - theTarget.y);
    if ((xd*xd+yd*yd) <= desiredseparation) { // target has been eaten
      return true;
    }
  }
  return false;
}
 
class Wall {
  PVector loc1;
  PVector loc2;
  color wallColor;
 
 
  Wall(float x1, float y1, float x2, float y2) {
    wallColor = color(127);
    loc1 = new PVector(round(x1/gridSnap)*gridSnap, round(y1/gridSnap)*gridSnap);
    loc2 = new PVector(round(x2/gridSnap)*gridSnap, round(y2/gridSnap)*gridSnap);
  }
 
  void display() {
 
    stroke(wallColor);
    line(loc1.x, loc1.y, loc2.x, loc2.y);
  }
 
  void setColorBlack() {
    wallColor = color(100);
  }
 
  void setColorRed() {
    wallColor = color(255, 0, 0);
  }
}
 
class Creature {
 
  // All the usual stuff
  PVector loc;
  PVector velocity;
  PVector acceleration;
  float r;
  float maxforce;    // Maximum steering force
  float maxspeed;    // Maximum speed
  color bodyColor;
  int nMembranePoints;
  float[] membraneDeviation;
  float[] membraneLocX;
  float[] membraneLocY;
  int creatureType;
 
  // Constructor initialize all values
  Creature(float x, float y, int theCreatureType, float radius) {
    loc = new PVector(x, y);
    r = radius;
    maxspeed = 3;
    maxforce = 0.2;
    acceleration = new PVector(0, 0);
    velocity = new PVector(0, 0);
    nMembranePoints = 32; // make this even number, preferably 2^n
    creatureType = theCreatureType;
 
    // make it look like a funky creature
    bodyColor = color(random(255), random(255), random(255)); // set a random color
    if (theCreatureType == PREY) {
      bodyColor = color(0, random(200, 255), 0); // set a random color
      maxforce /= 4;
      maxspeed /= 4;
      nMembranePoints /= 2;
    }
    // precalc location for random membrane
 
    membraneDeviation = new float[nMembranePoints];
    membraneLocX = new float[nMembranePoints];
    membraneLocY = new float[nMembranePoints];
 
    for (int i = 0; i < nMembranePoints; i++) {
      membraneDeviation[i] = 0.0;
      float angle = i * (360 / nMembranePoints);
      membraneLocX[i] = cos(radians(angle));
      membraneLocY[i] = sin(radians(angle));
    }
  }
 
  PVector getLoc() {
    return loc;
  }
 
  void applyForce(PVector force) {
    // We could add mass here if we want A = F / M
    acceleration.add(force);
  }
 
  float getR() {
    return r;
  }
 
  PVector getClosestTarget(ArrayList<Creature> creatures, PVector currentLocation, int myType) {
    PVector theTarget;
    PVector bestTarget = null;
    float d;
    float closest = LARGENUM;
    for (int i = 0; i < creatures.size (); i++) {
      theTarget = creatures.get(i).getLoc();
      d = theTarget.dist(currentLocation, theTarget);
      if (d < closest) {
        if (myType != creatures.get(i).creatureType) {
          bestTarget = theTarget;
          closest = d;
        }
      }
    }
    if (bestTarget == null && myType == PREDATOR) {
      for (int i = 0; i < 2; i++) {
        addRandomPrey();
      }
    }
    return bestTarget;
  }
 
 
  void applyBehaviors(ArrayList<Creature> creatures, ArrayList<Wall> walls) {
 
    PVector separateForce = separate(creatures, walls);
    PVector theTarget = getClosestTarget(creatures, loc, creatureType);
 
    if (theTarget == null) {
      theTarget = loc;
    }
 
    PVector seekForce = seek(theTarget);
    //line(loc.x, loc.y, theTarget.x, theTarget.y); // debug
 
    separateForce.mult(2);
    seekForce.mult(1);
    applyForce(separateForce);
    applyForce(seekForce);
 
    //collideWall(walls); // debug
  }
 
  // A method that calculates a steering force towards a target
  // STEER = DESIRED MINUS VELOCITY
  PVector seek(PVector theTarget) {
    PVector desired = PVector.sub(theTarget, loc);  // A vector pointing from the location to the target
 
    // Normalize desired and scale to maximum speed
    desired.normalize();
    desired.mult(maxspeed);
    // Steering = Desired minus velocity
    PVector steer = PVector.sub(desired, velocity);
    steer.limit(maxforce);  // Limit to maximum steering force
    if (creatureType == PREY) {
      steer.mult(-1.0); // go away from predator
    }
    return steer;
  }
 
  // Separation
  // Method checks for nearby creatures and steers away
  PVector separate (ArrayList<Creature> creatures, ArrayList<Wall> walls) {
    float desiredseparation = r*2;
    PVector sum = new PVector();
    int count = 0;
    // For every boid in the system, check if it's too close
    Creature other;
    for (int i = 0; i < creatures.size(); i++) {
      other = creatures.get(i);
      float d = PVector.dist(loc, other.loc);
      desiredseparation = r + other.r;
      // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
      if ((d > 0) && (d < desiredseparation) ) {
        if (creatureType == other.creatureType) {
          // Calculate vector pointing away from neighbor
          PVector diff = PVector.sub(loc, other.loc);
          diff.normalize();
          diff.div(d);        // Weight by distance
          sum.add(diff);
          count++;            // Keep track of how many
        } else if ( (creatureType == PREDATOR) && (other.creatureType == PREY) ) { // eat the prey
          creatures.remove(i);
        }
      }
    }
 
    // for each wall, check to see if it is too close
    desiredseparation = r;
    PVector closest;
    for (Wall w : walls) {
      //if ( isCollision(w.loc1.x, w.loc1.y, w.loc2.x, w.loc2.y, location.x, location.y, r) ) {
      closest = getClosestPointToCenter(w.loc1.x, w.loc1.y, w.loc2.x, w.loc2.y, loc.x, loc.y, r);
      float d = closest.dist(loc, closest);
      if ( d <= desiredseparation ) {
        // Calculate vector pointing away from neighbor
        PVector diff = PVector.sub(loc, closest);
        diff.normalize();
        diff.div(d);        // Weight by distance
        sum.add(diff);
        count++;            // Keep track of how many
      }
    }  
 
    // Average -- divide by how many
    if (count > 0) {
      sum.div(count);
      // Our desired vector is the average scaled to maximum speed
      sum.normalize();
      sum.mult(maxspeed);
      // Implement Reynolds: Steering = Desired - Velocity
      sum.sub(velocity);
      sum.limit(maxforce);
    }
    return sum;
  }
 
  // if creature collides with wall, then bounce away
  void collideWall(ArrayList<Wall> walls) {
    PVector closest;
    for (Wall w : walls) {
      //if ( isCollision(w.loc1.x, w.loc1.y, w.loc2.x, w.loc2.y, location.x, location.y, r) ) {
      closest = getClosestPointToCenter(w.loc1.x, w.loc1.y, w.loc2.x, w.loc2.y, loc.x, loc.y, r);
      if ( closest.dist(loc, closest) <= r ) {
        w.setColorRed();
      } else {
        w.setColorBlack();
      }
    }
  }
 
 
 
  // Method to update location
  void update() {
    // Update velocity
    velocity.add(acceleration);
    // Limit speed
    velocity.limit(maxspeed);
    loc.add(velocity);
    // Reset accelertion to 0 each cycle
    acceleration.mult(0);
  }
 
  void display() {
    // draws the creature
 
 
    fill(bodyColor);
    stroke(0);
    pushMatrix();
 
    translate(loc.x, loc.y);
    float theta = velocity.heading2D() + radians(90);
    rotate(theta);
    beginShape();
    float len = 0;
    float deviation = 0;
    float locX, locY;
 
 
    // draw the body
    for (int i = 0; i < nMembranePoints; i++) {
      deviation = membraneDeviation[i];
      deviation += r*0.05*random(-1, 1);
      deviation *= 0.95;
      membraneDeviation[i] = deviation;
      len = r + deviation;
      locX = membraneLocX[i];
      locY = membraneLocY[i];
      vertex(len*locX, len*locY);
    }
    endShape(CLOSE);
    // draw the eyes
 
    stroke(0);
    int eyeIndex = round(nMembranePoints*5/8);
    float eyeDist = 0.6;
    len = r + membraneDeviation[eyeIndex];
    locX = membraneLocX[eyeIndex];
    locY = membraneLocY[eyeIndex];
    fill(255);
    ellipse(len*locX*eyeDist, len*locY*eyeDist, len/1.5, len/1.5);
 
    fill(0);
    ellipse(len*locX*eyeDist, len*locY*eyeDist, len/(4+(1-creatureType)*4), len/(4+(1-creatureType)*4));
    eyeIndex = round(nMembranePoints*7/8);
    len = r + membraneDeviation[eyeIndex];
    locX = membraneLocX[eyeIndex];
    locY = membraneLocY[eyeIndex];
    fill(255);
    ellipse(len*locX*eyeDist, len*locY*eyeDist, len/1.5, len/1.5);
    fill(0);
    ellipse(len*locX*eyeDist, len*locY*eyeDist, len/(4+(1-creatureType)*4), len/(4+(1-creatureType)*4));
    popMatrix();
  }
}
 
//
// A useful class to intersect lines and circles or to gest the closest point to a circle in a given segment
//
//author      Ale Gonzlez
//
PVector getClosestPointToCenter(float x1, float y1, float x2, float y2, float cx, float cy, float r) {
  float dx;
  float dy;
  float sLength;
  float sx;
  float sy;
  float pLength;
  dx = x2-x1;
  dy = y2-y1;
  sLength = sqrt(dx*dx + dy*dy);
  sx = dx/sLength;
  sy = dy/sLength;
  pLength = (cx-x1)*sx + (cy-y1)*sy;
 
  if (pLength < 0) {
    return new PVector(x1, y1);
  } else if (pLength > sLength) {
    return new PVector(x2, y2);
  }
 
  return new PVector(x1 + sx*pLength, y1 + sy * pLength);
}
