Your browser does not support the canvas tag.

previous        Show / Hide Source        Download        next
//=====================================//
//          Outcat by Brian Ho         //
//=====================================//

Player player = new Player();
Camera camera = new Camera();
float worldSize = 2000; //square world
boolean keyUp, keyLeft, keyRight, keyDown;
Animol[] animols = new Animol[24];
Group bears;
Group dogs;
Group fish;
Group cows;

//class Keys {boolean up = false; boolean down = false; boolean left = false; boolean right = false;}
//Keys keys = new Keys();

void setup(){
  size(400,400,P2D);
  smooth(5);
  frameRate(60);
  noStroke();
  animols[0] = new Animol(150, -175);
  animols[1] = new Animol(250, -155);
  animols[2] = new Animol(130, -90);
  animols[3] = new Animol(180, -60);
  animols[4] = new Animol(70, -130);
  animols[5] = new Animol(110, -185);
  animols[6] = new Animol(60, 0);
  Animol[] tempBears = {animols[0],animols[1],animols[2],animols[3],animols[4],animols[5],animols[6]};
  bears = new Group(tempBears,0);
  
  animols[7] = new Animol(-100, -145);
  animols[8] = new Animol(-250, -70);
  animols[9] = new Animol(-135, -90);
  animols[10] = new Animol(-115, -43);
  animols[11] = new Animol(-180, -130);
  animols[12] = new Animol(-215, -140);
  Animol[] tempDogs = {animols[7],animols[8],animols[9],animols[10],animols[11],animols[12]};
  dogs = new Group(tempDogs,1);
  
  animols[13] = new Animol(-400, -145);
  animols[14] = new Animol(-350, -270);
  animols[15] = new Animol(-437, -290);
  animols[16] = new Animol(-380, -243);
  animols[17] = new Animol(-460, -330);
  animols[18] = new Animol(-415, -340);
  Animol[] tempFish = {animols[13],animols[14],animols[15],animols[16],animols[17],animols[18]};
  fish = new Group(tempFish,2);
  
  animols[19] = new Animol(100, 245);
  animols[20] = new Animol(57, 270);
  animols[21] = new Animol(137, 290);
  animols[22] = new Animol(-50, 220);
  animols[23] = new Animol(80, 330);

  Animol[] tempCows = {animols[19],animols[20],animols[21],animols[22],animols[23]};
  cows = new Group(tempCows,3);
}

void draw(){
  //update all
  player.update();
  camera.position = new PVector(player.position.x - 200,player.position.y - 200);
  bears.update();
  dogs.update();
  fish.update();
  cows.update();
  for(int i = 0; i < animols.length; i++){
    animols[i].update();
  }
  
  //drawing
  background(0);
  createParallaxGrid();
  bears.display();
  dogs.display();
  fish.display();
  cows.display();
  for(int i = 0; i < animols.length; i++){
    animols[i].display();
  }
  player.display();
}


void createParallaxGrid(){
  //small grid
  float gridSize = 90;
  float parallaxCoef = 0.5;
  for(float y = 0; y < worldSize*2; y += gridSize){
    for(float x = 0; x < worldSize*2; x += gridSize){
      PVector gridPos = camera.camCoords(new PVector(x-worldSize/2,y-worldSize/2));
      fill(40);
      ellipse(gridPos.x*parallaxCoef, gridPos.y*parallaxCoef, 5, 5);
    }
  }
  //large grid
  gridSize = 40;
  for(float y = 0; y < worldSize; y += gridSize){
    for(float x = 0; x < worldSize; x += gridSize){
      PVector gridPos = camera.camCoords(new PVector(x-worldSize/2,y-worldSize/2));
      fill(70);
      ellipse(gridPos.x, gridPos.y, 7, 7);
    }
  }  
}

//Input handling
void keyReleased(){
  if(keyCode == UP) keyUp = false;
  if(keyCode == DOWN) keyDown = false;
  if(keyCode == LEFT) keyLeft = false;
  if(keyCode == RIGHT) keyRight = false;
}
void keyPressed(){
    if(keyCode == UP) keyUp = true;
  if(keyCode == DOWN) keyDown = true;
  if(keyCode == LEFT) keyLeft = true;
  if(keyCode == RIGHT) keyRight = true;
}

float clamp(float value, float min, float max){
  if(value > max) return max;
  else if(value < min) return min;
  else return value;
}
class Animol{
  public PVector position = new PVector(0,0);
  PVector speed = new PVector(0,0);
  float acel = 0.5;
  float maxSpeed = .8;
  float fricCoef = 0.85;
  PVector toPlayer;
  PVector lookDir;
  Group group;
  
  PVector wanderDir = new PVector(random(1)-.5,random(1)-.5);
  
  Animol(float x, float y){
    position.x = x;
    position.y = y;
  }
  
  public void update(){
    toPlayer = new PVector(player.position.x - position.x, player.position.y - position.y);

    switch(group.stage){ 
    case 3 :
      lookDir =  new PVector(group.center().x - position.x, group.center().y - position.y); //look at eachother if in stage 3
      break;
    case 2 :
      lookDir = group.moveDirection;
      //maxSpeed = 300/(toPlayer.mag()+100);
      speed.add(group.moveDirection);
      speed.add(wanderDir);
    break;
    case 1:
      lookDir = new PVector(player.position.x - position.x, player.position.y - position.y); //look at player if in stage 1;
      break;
    }
    
    lookDir.normalize();
    lookDir.mult(3);
    
    //impose speed limit
    if(speed.mag() > maxSpeed){
      speed.normalize();
      speed.mult(maxSpeed);
    }
    
    position.add(speed);
    speed.mult(fricCoef);
  }
  
  public void display(){
    PVector camPos = camera.camCoords(position);
    ellipseMode(CENTER);
    fill(255);
    ellipse(camPos.x,camPos.y,25,25);
    switch(group.species){
      case 0 : //bear
        ellipse(camPos.x +9, camPos.y -9, 6, 6);
        ellipse(camPos.x -9, camPos.y -9, 6, 6);
        break;
      case 1 : //dog
        triangle(camPos.x - 18, camPos.y - 10, camPos.x - 12, camPos.y - 10, camPos.x - 16, camPos.y - 1);
        triangle(camPos.x + 18, camPos.y - 10, camPos.x + 12, camPos.y - 10, camPos.x + 16, camPos.y - 1);
        break;
      case 2 : //fish
        triangle(camPos.x - 14, camPos.y + 15, camPos.x - 10, camPos.y + 15, camPos.x - 10, camPos.y + 8);
        triangle(camPos.x + 14, camPos.y + 15, camPos.x + 10, camPos.y + 15, camPos.x + 10, camPos.y + 8);
        triangle(camPos.x + 14, camPos.y, camPos.x + 20 + sin(frameCount*.2), camPos.y - 6, camPos.x+20 + sin(frameCount*.2), camPos.y+6);
        break;
      case 3 : //goat
        ellipse(camPos.x +10, camPos.y -9, 8, 4);
        ellipse(camPos.x -10, camPos.y -9, 8, 4);
        triangle(camPos.x - 10, camPos.y-5, camPos.x-6,camPos.y - 15, camPos.x-1, camPos.y-10);
        triangle(camPos.x + 10, camPos.y-5, camPos.x+6,camPos.y - 15, camPos.x+1, camPos.y-10);
        break;
    }
    
    fill(0);
    //eyes face in speed direction
    ellipse(camPos.x - 4 + lookDir.x, camPos.y + lookDir.y,3,3);
    ellipse(camPos.x + 4 + lookDir.x, camPos.y + lookDir.y,3,3);
    if(group.stage == 3){ //content face
      //mouth
      rectMode(CENTER);
      ellipse(camPos.x - 2 + lookDir.x, camPos.y +6 + lookDir.y,4,4);
      ellipse(camPos.x + 2 + lookDir.x, camPos.y +6 + lookDir.y,4,4);
      fill(255);
      ellipse(camPos.x - 2 + lookDir.x, camPos.y +5 + lookDir.y,3,3);
      ellipse(camPos.x + 2 + lookDir.x, camPos.y +5 + lookDir.y,3,3);
    }
    else if(group.stage == 1){ //discontent face
      fill(0);
      rect(camPos.x+lookDir.x, camPos.y+lookDir.y+6,1,3);
      rect(camPos.x+lookDir.x, camPos.y+lookDir.y+8,5,1);
    }
    else if(group.stage == 2){ //GRRR angery
      //eyebrows
      stroke(0);
      line(camPos.x+lookDir.x - 8, camPos.y+lookDir.y-6,camPos.x+lookDir.x - 2,camPos.y+lookDir.y-4);
      line(camPos.x+lookDir.x + 8, camPos.y+lookDir.y-6,camPos.x+lookDir.x + 2,camPos.y+lookDir.y-4);
      noStroke();
      //mouth
      fill(0);
      rect(camPos.x+lookDir.x, camPos.y+lookDir.y+6,1,3);
      triangle(camPos.x+lookDir.x, camPos.y+lookDir.y+5,camPos.x+lookDir.x+4, camPos.y+lookDir.y+8,camPos.x+lookDir.x-4, camPos.y+lookDir.y+8);
      fill(255);
      triangle(camPos.x+lookDir.x, camPos.y+lookDir.y+7,camPos.x+lookDir.x+4, camPos.y+lookDir.y+10,camPos.x+lookDir.x-4, camPos.y+lookDir.y+10);
    }
    //nose
    fill(0);
    ellipse(camPos.x+lookDir.x, camPos.y+lookDir.y+4,4,2);
  }
  
  public void setGroup(Group _group){
    this.group = _group;
  }
  
  public void genWanderDir(){
    wanderDir = new PVector(random(1)-.5,random(1)-.5);
  }
}
class Camera {
  PVector position = new PVector(200,200);
  
  public PVector camCoords(PVector world){
    return new PVector( world.x - position.x, world.y - position.y);
  }
}
class Group { //helps the cliques organise themselves
  public Animol[] groupMembers;
  ArrayList<Triangle> tris = new ArrayList<Triangle>();
  private Timer stage1Timer = new Timer(2500); //1 second

  public int stage = 3;
  public int species = 1;
  public PVector moveDirection = new PVector(0, 0);
  PVector stage2PlayerPoint;

  Group(Animol[] animols, int spec) {
    species = spec;
    groupMembers = animols;
    for (Animol animol : groupMembers) {
      animol.setGroup(this);
    }
    generateAllTriangles();
  }

  public void display() {
    stroke(255, 100, 100, 140);
    if (stage == 1 || stage == 3) {
      for (Triangle tri : tris) {
        tri.display();
      }
    }
    noStroke();
  }

  public void update() {
    stage1Timer.update();

    PVector myCenter=center();
    myCenter.sub(player.position);
    float myCenterMag=myCenter.mag();
    if (stage == 3 && myCenterMag < 300) {
      for (Triangle tri : tris) {
        //check for collision
        if (tri.getLineAB().circleCollision(player.position, 13)||
          tri.getLineBC().circleCollision(player.position, 13)||
          tri.getLineCA().circleCollision(player.position, 13)) {
          stage = 1;
          stage1Timer.start();
        }
      }
    }
    //start stage 2
    if (stage1Timer.finished && stage == 1) {
      stage = 2;
      player.avoidTriggers ++;
      //stage2PlayerPoint = player.position.copy();
      stage2PlayerPoint = new PVector(player.position.x, player.position.y);
      //moveDirection = center().sub(stage2PlayerPoint).normalize();

      PVector moveDirection=center();
      moveDirection.sub(stage2PlayerPoint);
      moveDirection.normalize();

      for (Animol animol : groupMembers) {
        animol.genWanderDir();
      }
    }
    //stage 2
    if (stage == 2) {
      //float toCenter = center().sub(stage2PlayerPoint).mag();


      PVector toCenterVector=center();
      toCenterVector.sub(stage2PlayerPoint);
      float toCenter=toCenterVector.mag();
      
      //condition to switch to stage 3
      if (stage == 2 && toCenter > 300) {
        stage = 3;
        generateAllTriangles();
      }
    }
  }

  public PVector center() {
    float totalX = 0;
    float totalY = 0;
    for (Animol animol : groupMembers) {
      totalX += animol.position.x;
      totalY += animol.position.y;
    }
    return new PVector(totalX/groupMembers.length, totalY/groupMembers.length);
  }

  public Animol closest(PVector point) {
    float leastMag = 2000;
    Animol closestOne = null;
    for (Animol animol : groupMembers) {
      if (point.dist(animol.position) < leastMag) {
        leastMag = point.dist(animol.position);
        closestOne = animol;
      }
    }
    return closestOne;
  }

  public void generateAllTriangles() {
    tris = new ArrayList<Triangle>();
    for (int a = 0; a < groupMembers.length; a++) {
      PVector posA = groupMembers[a].position;
      for (int b = 0; b < groupMembers.length; b++) {
        PVector posB = groupMembers[b].position;
        if (posB == posA) break;
        for (int c = 0; c < groupMembers.length; c++) {
          PVector posC = groupMembers[c].position;
          if (posC == posA || posC == posB) break;

          Triangle dTris = new Triangle(posA, posB, posC);

          PVector center = dTris.getCircumcircleCenter();
          fill(0, 0, 0, 0);

          float radius = center.dist(posA);
          boolean delauneyCircumcircle = true;
          for (int p = 0; p < groupMembers.length; p++) {
            if (groupMembers[p].position.dist(center) < radius * .99) {
              delauneyCircumcircle = false;
              break;
            }
          }    
          if (delauneyCircumcircle) {
            tris.add(dTris);
          }
        }
      }
    }
  }
}
class Line {
  PVector a;
  PVector b;

  Line(PVector _a, PVector _b) {
    a = _a;
    b = _b;
  }

  public void display() {
    PVector camA = camera.camCoords(a);
    PVector camB = camera.camCoords(b);
    line(camA.x, camA.y, camB.x, camB.y);
  }

  public float slope() {
    float maxX = (b.x > a.x) ? b.x : a.x;
    float minX = (b.x > a.x) ? a.x : b.x;
    float maxY = (b.y > a.y) ? b.y : a.y;
    float minY = (b.y > a.y) ? a.y : b.y;
    return (maxX - minX)/(maxY - minY);
  }

  public float parallelSlope() {
    float maxX = (b.x > a.x) ? b.x : a.x;
    float minX = (b.x > a.x) ? a.x : b.x;
    float maxY = (b.y > a.y) ? b.y : a.y;
    float minY = (b.y > a.y) ? a.y : b.y;
    return -(maxY - minY)/(maxX - minX);
  }

  public float yInt(float slope) {
    // c = y - mx
    return(a.y - slope*a.x);
  }

  //uses this tutorial: https://code.tutsplus.com/tutorials/quick-tip-collision-detection-between-a-circle-and-a-line--active-10546
  public boolean circleCollision(PVector position, float radius) {
    boolean collides = false;

    //uses vector projection to project the perpendicular distance of the circle
    //PVector distanceVectorA = position.copy().sub(a);
    //PVector line = b.copy().sub(a);
    //PVector perpLine = line.copy().normalize().rotate(HALF_PI);

    PVector distanceVectorA = new PVector(position.x, position.y);
    distanceVectorA.sub(a);

    PVector line = new PVector(b.x, b.y);
    line.sub(a);

    PVector perpLine = new PVector(line.x, line.y);
    perpLine.normalize();
    perpLine.rotate(HALF_PI);



    //dot product of normal and distance. This is the projection.
    float distanceToLine = perpLine.x * distanceVectorA.x + distanceVectorA.y * perpLine.y;

    //checks to see if the point is inside the line.
    //PVector distanceVectorB = position.copy().sub(b);

    PVector distanceVectorB = new PVector(position.x, position.y);
    distanceVectorB.sub(b);



    float dotAP = line.x * distanceVectorA.x + distanceVectorA.y * line.y;
    float dotBP = line.x * distanceVectorB.x + distanceVectorB.y * line.y;

    //if the distance to the center is less than the radius then it collides
    //if the dot products are like this then in means that the point is within the line. Not sure how this works, but it does.
    if (Math.abs(distanceToLine) < radius  && (dotAP >= 0 && dotBP <= 0))
      collides = true;

    return collides;
  }
}
class Player {
  public PVector position = new PVector(0, 0);
  PVector speed = new PVector(0, 0);
  float acel = 0.5;
  float maxSpeed = 2.2;
  float fricCoef = 0.87;
  public int avoidTriggers = 0; 

  PVector lookDir;

  public void update() {
    if (keyUp) speed.y -= acel;
    if (keyDown) speed.y += acel;
    if (keyLeft) speed.x -= acel;
    if (keyRight) speed.x += acel;

    //impose speed limit
    if (speed.mag() > maxSpeed) {
      speed.normalize();
      speed.mult(maxSpeed);
    }
    position.add(speed);
    speed.mult(fricCoef);
    lookDir=new PVector(speed.x, speed.y);
    lookDir.normalize();

    //lookDir = speed.copy().normalize();
    lookDir.mult(3);
  }

  public void display() {
    PVector camPos = camera.camCoords(position);
    ellipseMode(CENTER);
    fill(255);
    ellipse(camPos.x, camPos.y, 25, 25);
    triangle(camPos.x - 12, camPos.y-5, camPos.x-14, camPos.y - 15, camPos.x-1, camPos.y-10);
    triangle(camPos.x + 12, camPos.y-5, camPos.x+14, camPos.y - 15, camPos.x+1, camPos.y-10);
    drawExpression();
  }

  void drawExpression() {
    PVector camPos = camera.camCoords(position);
    fill(0);
    //eyes face in speed direction
    ellipse(camPos.x - 4 + lookDir.x, camPos.y + lookDir.y, 3, 3);
    ellipse(camPos.x + 4 + lookDir.x, camPos.y + lookDir.y, 3, 3);
    if (avoidTriggers < 1) {
      //mouth
      rectMode(CENTER);
      ellipse(camPos.x - 2 + lookDir.x, camPos.y +6 + lookDir.y, 4, 4);
      ellipse(camPos.x + 2 + lookDir.x, camPos.y +6 + lookDir.y, 4, 4);
      fill(255);
      ellipse(camPos.x - 2 + lookDir.x, camPos.y +5 + lookDir.y, 3, 3);
      ellipse(camPos.x + 2 + lookDir.x, camPos.y +5 + lookDir.y, 3, 3);
    } else if (avoidTriggers < 25) {
      //eyebrows
      stroke(0);
      line(camPos.x+lookDir.x - 6, camPos.y+lookDir.y-3, camPos.x+lookDir.x - 3, camPos.y+lookDir.y-6);
      line(camPos.x+lookDir.x + 6, camPos.y+lookDir.y-3, camPos.x+lookDir.x + 3, camPos.y+lookDir.y-6);
      noStroke();
      //mouth
      fill(0);
      rect(camPos.x+lookDir.x, camPos.y+lookDir.y+6, 1, 3);
      triangle(camPos.x+lookDir.x, camPos.y+lookDir.y+5, camPos.x+lookDir.x+4, camPos.y+lookDir.y+8, camPos.x+lookDir.x-4, camPos.y+lookDir.y+8);
      fill(255);
      triangle(camPos.x+lookDir.x, camPos.y+lookDir.y+7, camPos.x+lookDir.x+4, camPos.y+lookDir.y+10, camPos.x+lookDir.x-4, camPos.y+lookDir.y+10);
    } else { // if you do it 25 times you become angry
      //eyebrows
      stroke(0);
      line(camPos.x+lookDir.x - 6, camPos.y+lookDir.y-6, camPos.x+lookDir.x - 3, camPos.y+lookDir.y-3);
      line(camPos.x+lookDir.x + 6, camPos.y+lookDir.y-6, camPos.x+lookDir.x + 3, camPos.y+lookDir.y-3);
      noStroke();
      //mouth
      fill(0);
      rect(camPos.x+lookDir.x, camPos.y+lookDir.y+6, 1, 3);
      triangle(camPos.x+lookDir.x, camPos.y+lookDir.y+5, camPos.x+lookDir.x+4, camPos.y+lookDir.y+8, camPos.x+lookDir.x-4, camPos.y+lookDir.y+8);
      fill(255);
      triangle(camPos.x+lookDir.x, camPos.y+lookDir.y+7, camPos.x+lookDir.x+4, camPos.y+lookDir.y+10, camPos.x+lookDir.x-4, camPos.y+lookDir.y+10);
    }
    //nose
    fill(0);
    ellipse(camPos.x+lookDir.x, camPos.y+lookDir.y+4, 4, 2);
  }
}
class Timer{
  int timeTo;
  int totalTime;
  boolean started = false;
  
  public boolean finished = false;
  
  Timer(int timeInMillis){
    totalTime = timeInMillis;
  }
  
  public void update(){
    if(started && !finished){
      if(millis() >= timeTo){
        finished = true;
        started = false;
      }
    }
  }
  
  public void start(){
    if(started == false){
      started = true;
      timeTo = millis() + totalTime;
      finished = false;
    }
  }
  
}
class Triangle{
  
  public PVector a;
  public PVector b;
  public PVector c;
  
  private Line ab;
  private Line bc;
  private Line ca;
  
  Triangle(PVector _a, PVector _b, PVector _c){
    a = _a;
    b = _b;
    c = _c;
    ab = new Line(a,b);
    bc = new Line(b,c);
    ca = new Line(c,a);
  }
  
  public void display(){
    getLineAB().display();
    getLineBC().display();
    getLineCA().display();
  }
  
  public PVector getCircumcircleCenter(){
    Line line1 = getLineAB();
    Line line2 = getLineBC();

    PVector line1MidPoint = new PVector((line1.a.x + line1.b.x)/2, (line1.a.y + line1.b.y)/2);
    PVector line2MidPoint = new PVector((line2.a.x + line2.b.x)/2, (line2.a.y + line2.b.y)/2);
    float slope1 = -1*(line1.b.x - line1.a.x)/(line1.b.y - line1.a.y);
    float slope2 = -1*(line2.b.x - line2.a.x)/(line2.b.y - line2.a.y);
    float yint1 = line1MidPoint.y - slope1*line1MidPoint.x;
    float yint2 = line2MidPoint.y - slope2*line2MidPoint.x;

    float ix = (yint2 - yint1)/(slope1 - slope2);
    float iy = slope1 * ix + yint1;
    return new PVector(ix,iy);
  }
  
  public Line getLineAB(){
      ab = new Line(a,b);
      return ab;
  }
  
  public Line getLineBC(){
      bc = new Line(b,c);
      return bc;
  }
  
  public Line getLineCA(){
      ca = new Line(c,a);
      return ca;
  }
  
}