//Click the left and right mouse buttons to grow and shrink the objects, respectively

/*A large field of colliding circles.  The centers are shown as a dot.
  a transparent ring surrounds each dot, it shows the extends of the current
  radius, and also as a minor eraser on the board.  Clicking the mouse increases
  and decreases it's size.  When two circles strike each other, a line is drawn
  between their centers that has it's brightness and shade bound to the
  intensity of the collision.
*/


Ball[] wombat;  //named wombat because I like the name wombat, and it is easy to remember
int ballCount = 100; //number of shapes set by assignment
float colStr = 0; //Global tossed by collisions to know how hard the most recent was
float rad = 15; //Global radius because all are the same... makes updating cheaper

void setup(){
  size(400,400);
  background(0);
  colorMode(HSB);
  smooth();
  noFill();
  wombat = new Ball[ballCount];
  
  //Spread all 100 wombats about the field
  for(int i = 0; i < ballCount; i++){
    wombat[i] = new Ball(random(0, width), random(0,height));
    wombat[i].spur();  //Give them an arbitrary direction
  }

  
}

void draw(){
  
  //Allow the user to see various forms created by increasing
  //and decreasing the collision radius
  if (mousePressed){
    if (mouseButton == LEFT) {
      rad += .5;
      rad = constrain(2, rad, 30);
    } else if (mouseButton == RIGHT) {
    rad -= .5;
    rad = constrain(2, rad, 30);
    }
  }
  
  //Move the coords to the bottom left for easier to remember math.
  translate(0,height);
  scale(1,-1);
  
  //Update all the womabt positions
  for(int i = 0; i < ballCount; i++){
    wombat[i].update();
  }
  
  //Now see if any of them hit each other
  for(int i = 0; i < ballCount; i++){
    //Only the one's we haven't already checked
    for(int j = i+1; j < ballCount; j++){
        if (collide(wombat[i], wombat[j])){
          wombat[i].update(); //update them again now that they hit
          wombat[j].update();
          
          //Draw the line between them
          strokeWeight(2);
          //Base the hue and brightness on the force
          stroke (130+colStr*20, 180, colStr*255, 200);
          line(wombat[i].pos, wombat[j].pos);
        }
    }
  }
}

//Overload line() to make it easier to read when I want to draw between two points;
void line(Vector A, Vector B){
  line(A.x, A.y, B.x, B.y);
}

class Ball{
  Vector pos, vel; //One XY for where we are, and another for how much we want to move
  
  Ball(float X, float Y){
    pos = new Vector(X, Y);
    vel = new Vector();
  }
  
  //Check for the edges and bounce back so everything stays inside
  void wallCheck(){
    if( (0 > pos.x) || (width < pos.x)){
      vel.x = -vel.x;
      pos.x = constrain(pos.x, 0, width);
    }
    if((0 > pos.y) || (height < pos.y)){
      vel.y = -vel.y;
      pos.y = constrain(pos.y, 0, height);
    }
  }
  
  //General movement case
  void run(){
    wallCheck();
    pos.add(vel);
    //vel.mult(.99);  //Remove ground friction so there is always movement
    noStroke();
  }
  
  void update(){
    run();
    //Draw a dark circle to show the radius
    stroke(0,20);
    ellipse(pos.x, pos.y, rad*2, rad*2);
    stroke(255,20);
    
    //draw a white dot to see the center
    point(pos.x, pos.y);
  }
  
  //For if we need to kick a ball randomly (such as starting out)
  void spur(){
    vel.add(new Vector(random(-.5, .5), random(-.5, .5)));
  }
}

//Large collision function that does 3 main things.
//1: It returns true if there was a collision so at the global level,
//   we know what happened, and can do simple if's.
//2: resolves and updates two colliding objects
//3: updates the global colStr telling us how strong the last hit was
boolean collide(Ball A, Ball B){
  float disX = A.pos.x - B.pos.x;
  float disY = A.pos.y - B.pos.y;
  float disR = rad + rad;
  if(disR*disR > (disX*disX + disY*disY)){  //Cheapest collision check we can do, because this is run often
    Vector U1x, U1y, U2x, U2y;              //Temp component Vectors
    Vector temp = new Vector(disX, disY);   //Axis Vector that we project on 
    
    //While this is a near mimic of the above if, this is more costly.
    //As such, we only run it when we know there is a collision.
    float over = (disR - sqrt(disX*disX + disY*disY))*.5; //Find the overlap
    
    temp = temp.unit();                     // Unit vector.
    A.pos.add(temp.prod(over));             // Push A along the axis to outside of B.
    
    //Save out aDot, to be returned later
    float aDot = temp.dot(A.vel);
    
    U1x = temp.prod(aDot);       // Projections
    U1y = A.vel.dif(U1x);
    
    temp.mult(-1);                          // Flip the direction of the axis
    B.pos.add(temp.prod(over));             // Push B along the axis to outside of A.
    
    //Save out bDot
    float bDot = temp.dot(B.vel);
    
    U2x = temp.prod(bDot);      //B projections
    U2y = B.vel.dif(U2x);
    
    //Final Velocities.
    A.vel.set(U2x);  //swapped because collisions swap components along the axis
    A.vel.add(U1y);
    //A.vel.mult(.75);  //Removed lost energy so everything keeps moving
    
    B.vel = new Vector();
    B.vel.set(U1x);
    B.vel.add(U2y);
    //B.vel.mult(.75);
    
    //Abs the dot products then return, because they are scalars -1 to 1
    colStr = abs(aDot)+abs(bDot);  //Will yield 0-2;
    
    return true;
  } else {
    //No collision
    return false;
  }
}

//Make it easy to carry around a set of two floats and perform vector math on them
//Makes the collision system easier to understand
class Vector{
  float x, y;
  
  Vector(){
    x = 0;
    y = 0;
  }
  
  Vector(Vector V){
    x = V.x;
    y = V.y;
  }
  
  Vector(float X, float Y){
    x = X;
    y = Y;
  }
  
  void set(Vector V){
    x = V.x;
    y = V.y;
  }
  
  void add(Vector V){
    x += V.x;
    y += V.y;
  }
  
  void add(float X, float Y){
    x += X;
    y += Y;
  }
  
  Vector sum(Vector V){
    Vector temp = new Vector(x + V.x, y + V.y);
    return temp;
  }
  
  void sub(Vector V){
    x -= V.x;
    y -= V.y;
  }
  
  Vector dif(Vector V){
    Vector temp = new Vector(x - V.x, y - V.y);
    return temp;
  }
  
  void mult(float T){
    x *= T;
    y *= T;
  }
  
  Vector prod(float T){
    Vector temp = new Vector(x*T, y*T);
    return temp;
  }
  
  float magni(){
    float magnitude = sqrt(x*x+y*y);
    return magnitude; 
  }
  
  Vector unit(){
    Vector temp = new Vector(x,y);
    temp.mult(1/magni());
    return temp;
  }
  
  void normalize(){
    this.mult(1/magni());
  }
  
  float dot(Vector V){
    float temp = x*V.x + y*V.y;
    return temp;
  }
}
Exercise 12: Use arrays and for structures to control the motion of one hundred shapes.