{Software} Structures

/* 
   
   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 
   
   Ported to p5.js by Casey Reas
   11 July 2016
   p5.js 0.5.2
   
   Restored by Casey Reas <http://reas.com> 
   22 June 2016 
   Processing v.3.1.1 <http://processing.org> 
   
   Implemented by Robert Hodgin <http://flight404.com> 
   6 April 2004 
   Processing v.68 <http://processing.org> 
 
*/


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

var xStage = 600; // x dimension of applet 
var yStage = 600; // y dimension of applet 

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

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

var gravity; // Strength of gravitational pull 
var xGrav; // x point of center of gravity 
var yGrav; // y point of center of gravity 
var xGravOffset;

var angleOffset;
var initRadius;
var maxDistance;

var bgColor;

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

function setup() {
  createCanvas(600, 600);
  bgColor = 255;
  background(bgColor);
  frameRate(30);
  createCircles();
}

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

function draw() {
  if (mouseIsPressed) {
    createCircles();
  }
  background(bgColor);
  tellCirclesToBehave();
}

function createCircles() {
  gravity = .075;
  maxDistance = 150;
  angleOffset = random(360);
  //circle = new Circle[totalCircles]; 
  initRadius = 150;
  for (var i = 0; i < totalCircles; i++) {
    var initAngle = i * 3.6 + angleOffset + random(10);
    var initTheta = (-((initAngle) * PI)) / 180;
    var initxv = cos(initTheta) * initRadius;
    var inityv = sin(initTheta) * initRadius;
    var xPos = xMid + initxv;
    var yPos = yMid + inityv;
    circle[i] = new Circle(xPos, yPos, 0, 0, i);
  }
}

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

function Circle(xSent, ySent, xvSent, yvSent, indexSent) {
  this.x = xSent;  // Circle x position
  this.y = ySent;  // Circle y position 
  this.r = 2;  // Circle radius 
  this.index = indexSent;  // Circle global ID 
  this.xv = xvSent;// Current velocity along x axis
  this.yv = yvSent;// Current velocity along y axis 

  this.mightCollide = [];// Collision might happen 
  this.hasCollided = []; // Collision is happening 
  
  this.distances = []; // Storage for the distance between circles 
  this.angles = [];// Storage for the angle between two connected circles
  this.thetas = [];// Storage for the radians between two connected circles 
  
  this.numCollisions; // Number of collisions in one frame 
  this.numConnections; // Total number of collisions 

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

  this.alphaVar; // Late addition variable for alpha modification 

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

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


  this.behave = function() {
    this.move();
    this.areWeClose();
    this.areWeColliding();
    this.areWeConnected();
    this.applyGravity();
    this.render();
    this.reset();
  }

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

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

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

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

    this.cxv = 0.0;
    this.cyv = 0.0;

  }

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

    this.cxv = 0.0;
    this.cyv = 0.0;

    this.r = this.numConnections * .85 + 2;
  }


  this.applyGravity = function() {
    this.gAngle = findAngle(this.x, this.y, xMid, yMid);
    this.gTheta = (-(this.gAngle * PI)) / 180;
    this.gxv = cos(this.gTheta) * gravity;
    this.gyv = sin(this.gTheta) * gravity;
    this.xv += this.gxv;
    this.yv += this.gyv;
  }

  this.move = function() {
    this.x += this.xv;
    this.y += this.yv;
  }

  this.render = function() {

    noStroke();
    fill(0, 25);
    ellipse(this.x, this.y, this.r, this.r);
    fill(0 + this.r * 10, 50);
    ellipse(this.x, this.y, this.r * .5, this.r * .5);
    fill(0 + this.r * 10);
    ellipse(this.x, this.y, this.r * .3, this.r * .3);

    if (this.numCollisions > 0) {
      noStroke();
      fill(0, 25);
      ellipse(this.x, this.y, this.r, this.r);

      fill(0, 55);
      ellipse(this.x, this.y, this.r * .85, this.r * .85);
      fill(0);
      ellipse(this.x, this.y, this.r * .7, this.r * .7);
    }
    
    for (var i = 0; i < totalCircles; i++) {
      if (this.hasCollided[i] && i < this.index) {
        this.xd = this.x - circle[i].x;
        this.yd = this.y - circle[i].y;
        stroke(0, 150 - this.distances[i] * 2.0);
        noFill();
        beginShape();
        vertex(this.x, this.y);
        vertex(this.x - this.xd * .25 + random(-1.0, 1.0), this.y - this.yd * .25 + random(-1.0, 1.0));
        vertex(this.x - this.xd * .5 + random(-3.0, 3.0), this.y - this.yd * .5 + random(-3.0, 3.0));
        vertex(this.x - this.xd * .75 + random(-1.0, 1.0), this.y - this.yd * .75 + random(-1.0, 1.0));
        vertex(circle[i].x, circle[i].y);
        endShape();
        //line (x, y, circle[i].x, circle[i].y); 
      }
    }
    noStroke();
  }

  this.reset = function() {
    this.numCollisions = 0;
    this.numConnections = 0;
  }
}

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

function findAngle(x1, y1, x2, y2) {
  var xd = x1 - x2;
  var yd = y1 - y2;
  var t = atan2(yd, xd);
  var a = (180 + (-(180 * t) / PI));
  return a;
}