Return to menu
/*

   A surface filled with one hundred medium to small sized circles.
   Each circle has a different size and direction, but moves at the same slow rate.
   Display:
   A. The instantaneous intersections of the circles
   >>> B. The aggregate intersections of the circles

   Implemented by Robert Hodgin <http://flight404.com>
   7 April 2004
   Processing v.68 <http://processing.org>

*/

// ********************************************************************************
// INITIALIZE VARIABLES
// ********************************************************************************

int xStage         = 900;             // x dimension of applet
int yStage         = 500;             // y dimension of applet

int xMid           = xStage/2;        // x midpoint of applet
int yMid           = yStage/2;        // y midpoint of applet

int totalCircles   = 100;             // total number of circles
Circle[] circle;                      // Circle object array

float gravity;                        // Strength of gravitational pull
float xGrav;                          // x point of center of gravity
float yGrav;                          // y point of center of gravity
float xGravOffset;                    // offset var to warp gravitational field

float angleOffset;                    // sets the initial point of rotation for the creation of the circles
float initRadius;                     // the starting radius of the creation of the circles
float maxDistance;                    // maximum distance allowed for connections

color bgColor;

// BOOLEANS FOR TESTING PURPOSES
boolean clear      = false;

int timer          = 0;
int timerPause     = 750;
int timerMax       = 775;

// ********************************************************************************
// SETUP FUNCTION
// ********************************************************************************

void setup()
{
  size(900, 500);
  bgColor = color(20,20,20);
  background(bgColor);
  smooth();
  colorMode(RGB, 255);
  ellipseMode(CENTER_RADIUS);
  noStroke();
  framerate(30);
  createCircles();
}

// ********************************************************************************
// MAIN LOOP FUNCTION
// ********************************************************************************

void loop()
{
  if (clear){
    background(bgColor);
  }
  dealWithTimer();
  if (timer < timerPause){
    tellCirclesToBehave();
  }

}

void createCircles(){
  gravity                        = random(0.005,0.1);
  xGrav                          = xMid + random(-50,50);
  yGrav                          = yMid + random(-50,50);
  xGravOffset                    = random(1.0, 1.24);
  maxDistance                    = random(75,150);
  angleOffset                    = random(360);
  circle                         = new Circle[totalCircles];
  initRadius                     = random(100,140);
  for (int i=0; i<totalCircles; i++){
    float initAngle              = i * 3.6 + angleOffset;
    float initTheta              = (-((initAngle) * PI))/180;

    float initxv                 = cos(initTheta) * initRadius;
    float inityv                 = sin(initTheta) * initRadius;
    float xPos = xMid + initxv;
    float yPos = yMid + inityv;
    circle[i] = new Circle(xPos, yPos,0,0, i);
  }
}

void tellCirclesToBehave(){
  for (int i=0; i<totalCircles; i++){
    circle[i].behave();
  }
}

void dealWithTimer(){
  if (timer > timerMax){
    timer = 0;
    background(bgColor);
    createCircles();
  }
  timer ++;
}

class Circle
{
  int index;                    // Circle global ID

  float x;                      // Circle x position
  float y;                      // Circle y position
  float r;                      // Circle radius
  float rBase;                  // Circle original radius

  float angle;                  // Angle of movement in degrees
  float theta;                  // Angle of movement in radians
  float speed;                  // Speed of movement

  float xv;                     // Current velocity along x axis
  float yv;                     // Current velocity along y axis

  boolean[] mightCollide;       // Collision might happen
  boolean[] hasCollided;        // Collision is happening

  float[] distances;            // Storage for the distance between circles
  float[] angles;               // Storage for the angle between two connected circles
  float[] thetas;               // Storage for the radians between two connected circles
  int numCollisions;            // Number of collisions in one frame
  int numConnections;           // Total number of collisions

  float xd;                     // Distance to target along x axis
  float yd;                     // Distance to target along y axis
  float d;                      // Distance to target

  float alphaVar;               // Late addition variable for alpha modification

  float cAngle;                 // Angle of collision in degrees
  float cTheta;                 // Angle of collision in radians
  float cxv;                    // Collision velocity along x axis
  float cyv;                    // Collision velocity along y axis

  float gAngle;                 // Angle to gravity center in degrees
  float gTheta;                 // Angle to gravity center in radians
  float gxv;                    // Gravity velocity along x axis
  float gyv;                    // Gravity velocity along y axis

  Circle (float xSent, float ySent, float xvSent, float yvSent, int indexSent){
    x               = xSent;
    y               = ySent;
    r               = 4;

    index           = indexSent;

    xv              = xvSent;
    yv              = yvSent;

    alphaVar        = random(35);

    mightCollide    = new boolean[totalCircles];
    hasCollided     = new boolean[totalCircles];
    distances       = new float[totalCircles];
    angles          = new float[totalCircles];
    thetas          = new float[totalCircles];
  }

  void behave(){
    move();
    areWeClose();
    areWeColliding();
    areWeConnected();
    applyGravity();
    //move();               // this is where the move function should go, but for some reason having it come first makes for more interesting renders.
    render();
    reset();
  }

  void areWeClose(){
    for(int i=0; i<totalCircles; i++){
      if (i != index){
        if (abs(x - circle[i].x) < 50 && abs(y - circle[i].y) < 50){
          mightCollide[i] = true;
        } else {
          mightCollide[i] = false;
        }
      }
    }
  }

  void areWeColliding(){
    for(int i=0; i<totalCircles; i++){
      if (mightCollide[i] && i != index){
        distances[i] = findDistance(x, y, circle[i].x, circle[i].y);
        if (distances[i] < (r + circle[i].r) * 1.1){
          hasCollided[i]               = true;
          circle[i].hasCollided[index] = true;

          angles[i]                    = findAngle(x,y,circle[i].x,circle[i].y);
          thetas[i]                    = (-(angles[i] * PI))/180.0;
          cxv                         += cos(thetas[i]) * ((circle[i].r + r)/2.0);
          cyv                         += sin(thetas[i]) * ((circle[i].r + r)/2.0);
          numCollisions               += 1;
        }
      }
    }

    if (numCollisions > 0){
      xv = -cxv/numCollisions;
      yv = -cyv/numCollisions;
    }

    cxv = 0.0;
    cyv = 0.0;

  }

  void areWeConnected(){
    for(int i=0; i<totalCircles; i++){
      if (hasCollided[i] && i != index){
        distances[i] = findDistance(x, y, circle[i].x, circle[i].y);
        if (distances[i] < maxDistance){
          angles[i]                    = findAngle(x,y,circle[i].x,circle[i].y);
          thetas[i]                    = (-(angles[i] * PI))/180.0;
          cxv                         += cos(thetas[i]) * (circle[i].r/10.0);
          cyv                         += sin(thetas[i]) * (circle[i].r/10.0);
          numConnections              += 1;
        } else {
          hasCollided[i]               = false;
          circle[i].hasCollided[index] = false;
        }
      }
    }
    if (numConnections > 0){
      xv += (cxv/numConnections)/4.0;
      yv += (cyv/numConnections)/4.0;
    }

    cxv = 0.0;
    cyv = 0.0;

    r = numConnections + 1;
  }


  void applyGravity(){
    gAngle        = findAngle(x,y,xGrav,yGrav);
    gTheta        = (-(gAngle * PI))/180;
    gxv           = cos(gTheta) * gravity * xGravOffset;
    gyv           = sin(gTheta) * gravity;
    xv += gxv;
    yv += gyv;
  }

  void move(){
    x += xv;
    y += yv;
  }

  void render(){
    noFill();
    d = findDistance(x, y, xMid, yMid);
    if (d > initRadius){
      stroke(255 - d, d - alphaVar);
    } else {
      stroke(d, d - alphaVar);
    }
    point(x,y);
    noStroke();
    //fill(255,255);
    //ellipse(x, y, r, r);
    if (numCollisions > 0){
      fill(255,4);
      ellipse(x, y, r * 5.0, r * 5.0 + 5);
      ellipse(x, y, r * 3.0, r * 3.0 + 5);
      if (d > initRadius){
        fill(255,255);
      } else {
        fill(0,255);
      }
      ellipse(x, y, r * 0.5, r * 0.5);
    }

    for(int i=0; i<totalCircles; i++){
      if (hasCollided[i] && i < index){
        stroke(abs(angles[i] - 180)*1.3, 5);
        //stroke(abs(255 - d*.75),6);
        line (x, y, circle[i].x, circle[i].y);
      }
    }
    noStroke();
  }

  void reset(){
    numCollisions = 0;
    numConnections = 0;
  }
}

float findDistance(float x1, float y1, float x2, float y2){
  float xd = x1 - x2;
  float yd = y1 - y2;
  float td = sqrt(xd * xd + yd * yd);
  return td;
}

float findAngle(float x1, float y1, float x2, float y2){
  float xd = x1 - x2;
  float yd = y1 - y2;

  float t = atan2(yd,xd);
  float a = (180 + (-(180 * t) / PI));
  return a;
}