// ** NEW ** -> screenshots of different visualizations 
// of this program available @:
// http://users.design.ucla.edu/~lonergan/stuff/screenshots/

// BUGS
// 
// guthrie

// CLICK MOUSE to generate new, randomly colored bug
// TYPE 'D' to return to Default bug configuration
// I like to hit 'D' a lot

// RULES:

// The background color is the average color of all the bugs
// Bugs attempt to catch the nearest bug with the rarest, most unique color
// (as different from the background as possible : in hue, saturation and brightness)
// The rarer a bug's color, the bigger he is

// When two bugs collide they spawn a new bug
// The baby bug's color is the average of the parent bugs' colors
// (collisions do not always mean spawning, or immediate spawning...)
// (...or else the screen would fill up with bugs way too fast!)

// Bugs die when they get old, but the rarer-colored bugs survive longer
// When bugs walk too far off of the screen, they are erased from memory

// If a spawning frenzy occurs, and there are too many bugs concentrated in one spot, 
//      bugs will run away in the opposite direction
// Also, there is a gentle push on the more rare bugs towards the center of the screen




int focusX=-1,focusY=-1;
float focusTime=-1;

int numBugs=12;
int bugBuffer=400;
Bug[] bugs = new Bug[bugBuffer];
//PImage ladybug;
//int rad=5;  // collision margin
int margin=10; // initialize x,y margin

float redsum=0,greensum=0,bluesum=0;
float rs=10000000,gs=10000000,bs=10000000;

float recentExplosion=0;

float[] rareSort = new float[bugBuffer];

int count=0;

void setup()
{
  size(300,300);
  colorMode(RGB,255);
  ellipseMode(CENTER);
  angleMode(DEGREES);
  framerate(60);
  background(255,255,255);

  //ladybug = loadImage("chartreuse.gif");
  

  bugs[0] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),3.0,color(255),0,bugs);

  for(int i=1; i<numBugs; i++)
  {

    if(random(0,1)>.9)  // SUPER DIFFERENT BUG!!!!!!!!
    {
      bugs[i] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),3.0,color(random(0,255),random(0,255),random(0,255)),i,bugs);
    }
    else
    {
      bugs[i] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),3.0,color(random(150,200),random(0,255),random(0,255)),i,bugs);
    }
  }

  /*
  bugs[0] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),color(255,255,255),0,bugs);
  bugs[1] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),color(0,200,200),1,bugs);
  bugs[2] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),color(255,255,0),2,bugs);
  bugs[3] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),color(255,0,255),3,bugs);
  */

}

void draw()
{
  rs=smoother(rs,redsum/(numBugs+1),5,0,255);
  gs=smoother(gs,greensum/(numBugs+1),5,0,255);
  bs=smoother(bs,bluesum/(numBugs+1),5,0,255);

  if(numBugs>=bugBuffer)
  {
 

  }
        background(rs,gs,bs);


  /*
  float rare, maxRare=bugs[0].updateRareness();
  int index=0;

  // index = smallest rareness

  for(int i=1; i<numBugs; i++)
  {

    rare = bugs[0].updateRareness();
    if(rare<maxRare)
    {

      maxRare=rare;
      index=i;
    }
  }
  */


  for(int i=0; i<numBugs; i++)
  {  
  
    if(bugs[i].isDead())
    {

      eraseBug(i);
      i--;
    }

  }
  
  for(int i=0; i<numBugs; i++)
  {
    rareSort[i]=bugs[i].updateRareness();
  }

  for(int i=0; i<numBugs; i++)
  {
    bugs[i].update();

    bugs[i].draw();
  }

  //stroke(0);
  //beginShape(LINES);

  /*
  for(int i=0; i<numBugs; i++)
  {

    bugs[i].giveVertex();

  }*/
  //endShape();

  count++;

}

void defaultBugs()
{
  println("hi");
  numBugs=12;
  bugs[0] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),3.0,color(255),0,bugs);

  for(int i=1; i<numBugs; i++)
  {

    if(random(0,1)>.97)  // SUPER DIFFERENT BUG!!!!!!!!
    {
      bugs[numBugs] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),3.0,color(random(0,255),random(0,255),random(0,255)),i,bugs);
    }
    else
    {
      bugs[i] = new Bug(random(margin,width-margin),random(margin,height-margin),random(1,3),random(-.8,.8),random(-.8,.8),3.0,color(random(150,200),random(0,255),random(0,255)),i,bugs);
    }
  }

}

float smoother(float pos, float dest, float speed, int bottom, int top)
{

  float dif = dest - pos;
  if(abs(dif) > 1.0) {
    pos = pos + dif/speed;
  }
  return(constrain(pos, bottom, top));

}

void eraseBug(int which)
{
  bugs[which].funeral();
  bugs[which]=bugs[numBugs-1];
  numBugs--;
}

void mousePressed()
{
  /*
  focusX=mouseX;
  focusY=mouseY;
  focusTime=millis();
  */

  if(numBugs<bugBuffer)
  {
    if(random(0,1)>.97)  // SUPER DIFFERENT BUG!!!!!!!!
    {
      bugs[numBugs] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),1.4,color(random(0,255),random(0,255),random(0,255)),numBugs,bugs);
    }
    else if(random(0,1)>.93)  // whitey
    {
    bugs[numBugs] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),1.4,color(255),numBugs,bugs);
    
    
    }
    else
    {
      bugs[numBugs] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),1.4,color(random(150,200),random(0,255),random(0,255)),numBugs,bugs);
    }
    numBugs++;
  }
  else
  {
    if(random(0,1)>.97)
    {
      bugs[numBugs-1] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),1.4,color(random(0,255),random(0,255),random(0,255)),numBugs,bugs);

    }
     else if(random(0,1)>.93)  // whitey
    {
    bugs[numBugs-1] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),1.4,color(255),numBugs,bugs);
    
    
    }
    else
    {
      bugs[numBugs-1] = new Bug(mouseX,mouseY,random(1,3),random(-.8,.8),random(-.8,.8),1.4,color(random(150,200),random(0,255),random(0,255)),numBugs-1,bugs);
    }
  }
}

void keyPressed()
{
  if(keyCode=='d' || keyCode=='D')
  {
    for(int i=0; i<numBugs; i++)
    {
      eraseBug(i);
    
    }
    
      rs=10000000;
      gs=10000000;
      bs=10000000;
      redsum=0;
      greensum=0;
      bluesum=0;
      
    defaultBugs();
  }
}


/*** BUG CLASS OUTLINE **\

FIELDS:

float M = mass
float D = damper (used to get the physics on the right scale, speed-wise)
float x,y = x and y position
float fx,fy = force in the x and y directions
float vx,vy = velocity in the x and y directions
float ax,ay, = acceleration in the x and y directions

int me = index of this bug in the bugs array
float birthday = set when bug is born, used to work out when bug dies
color myColor = the color of the bug
float rareness = how rare bug's color is compared to the average bug color
float rad = "radius" of bug
float rr = real radius of bug, "rad" approaches this number smoothly

Bug[] others = the bugs array
int numClose = how many bugs are within a certain radius of it
int lastTouch = last bug collided with, to lessen repeat collisions
int mostAttractive = index of the bug which is the most rare within a certain radius

METHODS:

Bug() = constructor sets initial values, adds its own color to the average color sums, sets the bug's birthday
float updateRareness() = sets the rareness and rr (based on rareness) fields, returns rareness
float calcRareness() = returns the difference between myColor and the average color of all the bugs
void update() = handles physics, "AI", collisions, everything else
boolean isCollided() = determines if this bug has collided with a bug at a certain position
color avgColor() = returns the average between myColor and another color (for spawning)
void collisions() = handles collisions between bugs and spawning
void draw() = draws two ellipses that represent the bug
void setMostAttractive() = sets mostAttractive field to the rarest bug in a certain radius
void funeral() = takes a dying bug's color out of the average color of all the bugs
float distance() = returns distance between this bug and another position
float giveRareness() = returns rareness!
float giveX() = returns x!
float giveY() = returns y!
boolean isDead() = returns true if a bug has died by stepping too far out of bounds or being alive for too long

*/

class Bug
{
  float x,y,M,D=.48;
  float fx,fy;
  float vx,vy;
  float ax,ay;

  float rad=1.4;
  float rr=rad;

  float rareness=0;
  int numClose=0;

  float birthday;

  color myColor;

  int me;
  Bug[] others;
  int lastTouch=-1;
  int mostAttractive=-1;  // index of most attractive bug in immediate vicinity

  Bug(float myx, float myy, float myM, float myfx, float myfy, float myrad, color mymyColor, int myme, Bug[] myothers)
  {
    x=myx;
    y=myy;
    M=myM;
    fx=myfx;
    fy=myfy;
    myColor=mymyColor;
    me=myme;
    others=myothers;
    rad=myrad;

    redsum+=red(myColor);
    greensum+=green(myColor);
    bluesum+=blue(myColor);

    birthday=millis();

  }

  float updateRareness() {

    rareness = calcRareness();
    // rareness is the abs() difference between myColor and the average color of all the bugs
    // higher # == rarer

    rr=3.4*(.4+rareness/180);

    return rareness;

  }

  float calcRareness()
  {
    float calcr=abs(red(myColor)-rs);
    float calcg=abs(green(myColor)-gs);
    float calcb=abs(blue(myColor)-bs);
    
    if(abs((red(myColor)+255)-rs)<calcr)
      calcr=abs((red(myColor)+255)-rs);
      
    if(abs((green(myColor)+255)-gs)<calcg)
      calcg=abs((green(myColor)+255)-gs);
      
    if(abs((blue(myColor)+255)-bs)<calcb)
      calcb=abs((blue(myColor)+255)-bs);
      
    return(calcr%255 + calcg%255 + calcb%255);
 
    // rareness is the abs() difference between myColor and the average color of all the bugs
    // higher # == rarer
    // BUT!!!: rgbs are wraparound values... that is, red=0 is just as close to red=255 as it is to red=1
    // hence all the if statements
  }

  void update() {

    fx=smoother(fx,0,40,-10000,10000);
    fy=smoother(fy,0,40,-10000,10000);

    setMostAttractive();

    if(mostAttractive>=0)
    {

      float damper=.0005;
      float addfx=(others[mostAttractive].giveX()-x)*damper;
      float addfy=(others[mostAttractive].giveY()-y)*damper;

      if(numClose<random(30,55))
      {
        fx+=addfx;
        fy+=addfy;
      }
      else
      {

        fx-=addfx*1.5;
        fy-=addfy*1.5;
      }

    }

    if(rareness>random(90,400))
    {
      float damperB=.000087;
      float addfxB=(width/2-x)*damperB;
      float addfyB=(height/2-y)*damperB;
      fx+=addfxB;
      fy+=addfyB;

    }

    if(focusX>=0)
    {
      float damperB=(millis()-focusTime+1)/900000;

      float addfxB=(focusX-x)*damperB;
      float addfyB=(focusY-y)*damperB;
      fx+=addfxB;
      fy+=addfyB;
    }

    ax = fx / M;           // Set the acceleration, f=ma == a=f/m
    ay = (fy) / M;

    vx = D * (vx + ax);   // Set the velocity
    vy = D * (vy + ay);

    //    x = constrain(x + vx,rad/2,width-rad/2);
    //    y = constrain(y + vy,rad/2,height-rad/2);

    x=x+vx;
    y=y+vy;
    
    
  float dif = rr - rad;
  if(abs(dif) > .04) {
    rad = rad + dif/15;
  }

      
//    rad = smoother(rad,rr,15,0,10);
    /*
    if(rad<rr)
    rad+=.027;
    if(rad-.06>rr)
    rad-=.09;
    */
    

    collisions();  // with other dots

  }

  boolean isCollided(float otherx, float othery)
  {
    if(dist(otherx,othery,x,y)<(rad*2))
    return true;

    return false;
  }

  color avgColor(color othercolor)
  {

    return color((red(othercolor)+red(myColor))/(2),(green(othercolor)+green(myColor))/(2),(blue(othercolor)+blue(myColor))/(2));

  }

  void collisions()
  {
    boolean cloned=false;
    for(int i=0; i<numBugs && !cloned; i++)
    {
      if(i!=me && others[i].isCollided(x,y) && lastTouch!=i)
      {
        // COLLISION HAS OCCURED!
        lastTouch=i;
        cloned=true;

        if(numBugs<bugBuffer && (millis()-recentExplosion)>140)    // this last number: lower = bigger spawning explosions
        {
          recentExplosion=millis();
          bugs[numBugs] = new Bug(x,y,random(1,3),random(-.8,.8),random(-.8,.8),1.6,others[i].avgColor(myColor),numBugs,bugs);
          numBugs++;
        }
        rad+=.001;
        /*
        fx=-fx;
        vx=-vx;
        fy=-fy;
        vy=-vy;
        */
      }
    }
  }

  void draw()
  {

    float ang=atan(vy/vx);    // angle created by x and y forces

    /*
    if(me==0)
    {
      ellipse(x,y,rad*5,rad*5);

      if(mostAttractive>=0)
      {

        stroke(0);
        beginShape(LINES);
        giveVertex();
        others[mostAttractive].giveVertex();
        endShape();
      }

    }*/

    push();
    translate(x,y);


    if(vx>0)
    rotate(90+ang);
    else
    rotate(ang-90);

    rotate(cos(millis()*((abs(vx)+abs(vy))*4))*(7+6*noise(me*2000)));  // makes the bug waddle

   

    noStroke();
    fill(0);
    ellipse(0,-rad,rad*1.5,rad);

    //   stroke(red(myColor)-50,green(myColor)-50,blue(myColor)-50);
    //   stroke(0,(5-rad)*12);
    //   stroke(0,55);
    //   stroke(0,64-(numClose/2.5));
    //   stroke(0,(500-rareness)/20);
     
    fill(myColor);
    /*
    vertex(0,rad+rad*(3.5/2));
    vertex(-rad,rad);
    vertex(0,rad-rad*(3.5/2));
    vertex(rad,rad);
    vertex(0,rad+rad*(3.5/2));
    */
    
    ellipse(0,rad,rad*2,rad*3.5);

    pop();

  }

  void setMostAttractive()
  // finds and records the index of the most rare bug in a certain radius
  // most attractive bug must be rarerer than itself
  // most attractive bug must be above a threshhold "minRareness"

  // also sets numClose -- how many bugs are really close to it
  {
    float searchRadius=120;
    float minRareness=170;
    float bestRareness=0;
    int bestIndex=-1;
    numClose=0;

    for(int i=0; i<numBugs; i++)
    {
      float thisRareness = others[i].giveRareness();
      if(others[i].distance(x,y)<searchRadius && i!=me)
      {
        numClose++;
        if(thisRareness>bestRareness && thisRareness>minRareness)// && thisRareness>rareness)
        {
          bestRareness=thisRareness;
          bestIndex=i;
        }
      }
    }
    mostAttractive=bestIndex;

  }

  void funeral()
  {
    redsum-=red(myColor);
    greensum-=green(myColor);
    bluesum-=blue(myColor);
  }

  float distance(float otherx, float othery)
  {
    return dist(otherx,othery,x,y);
  }

  float giveRareness()
  {
    return rareness;
  }

  float giveX()
  {
    return x;
  }

  float giveY()
  {
    return y;
  }
  
  boolean isDead()
  {
    if(x<=-margin || x>=width+margin || y<=-margin || y>=height+margin)
    {
      return true;
    }
    else if(rareness>0 && millis()-birthday>random(rareness*1500,rareness*3000))
   {
     //println(millis()-birthday);
      return true;
    }
    else
    {

    return false;
    }
  }

}
Exercise 4.C: Objects
Design a class which displays, animates, and defines the behavior of a machine or organism. Give your invention a goal (e.g. finding other creatures, searching for food, climbing the screen) and have it react when it reaches its goal.