{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
   13 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 = 900; // x dimension of applet 
var yStage = 500; // 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;

// BOOLEANS FOR TESTING PURPOSES 
var clearScreen = false;

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

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

function setup() {
  createCanvas(900, 500);
  bgColor = 20;
  background(bgColor);
  frameRate(30);
  createCircles();
}

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

function draw() {
  if (clearScreen) {
    background(bgColor);
  }
  dealWithTimer();
  if (timer < timerPause) {
    tellCirclesToBehave();
  }
}

function 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);
  initRadius = random(130, 140);
  for (var i = 0; i < totalCircles; i++) {
    var initAngle = i * 3.6 + angleOffset;
    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 dealWithTimer(){ 
  if (timer > timerMax){ 
    timer = 0; 
    background(bgColor); 
    createCircles(); 
  } 
  timer++; 
}

function Circle(xSent, ySent, xvSent, yvSent, indexSent) {
  this.index = indexSent; // Circle global ID 
  
  this.x = xSent; // Circle x position
  this.y = ySent; // Circle y position 
  this.r = 4; // Circle radius 
  
  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 = 0; // Number of collisions in one frame 
  this.numConnections = 0; // Total number of collisions 

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

  this.alphaVar = random(35); // Late addition variable for alpha modification 

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

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


  this.behave = function() {
    this.move();
    this.areWeClose();
    this.areWeColliding();  // This is the current area of trouble
    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, xGrav, yGrav);
    this.gTheta = (-(this.gAngle * PI)) / 180;
    this.gxv = cos(this.gTheta) * gravity * xGravOffset;
    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() {

    noFill(); 
    this.d = findDistance(this.x, this.y, xMid, yMid); 
    if (this.d > initRadius){ 
      stroke(255 - this.d, this.d - this.alphaVar); 
    } else { 
      stroke(this.d, this.d - this.alphaVar); 
    } 
    point(this.x,this.y); 
    noStroke(); 
    if (this.numCollisions > 0){ 
      fill(255, 4); 
      ellipse(this.x, this.y, this.r * 5.0, this.r * 5.0 + 5); 
      ellipse(this.x, this.y, this.r * 3.0, this.r * 3.0 + 5); 
      if (this.d > initRadius){ 
        fill(255,255); 
      } else { 
        fill(0,255); 
      } 
      ellipse(this.x, this.y, this.r * 0.5, this.r * 0.5); 
    } 
    
    for (var i = 0; i < totalCircles; i++){ 
      if (this.hasCollided[i] && i < this.index){ 
        stroke(abs(this.angles[i] - 180)*1.3, 5); 
        line (this.x, this.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;
}