{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 William Ngan <http://metaphorical.net> 
   4 April 2004 
   Processing v.68 <http://processing.org> 
 
*/

var field = [];
var fieldShade = [];
var gaph, gapv;
var marginh, marginv; // margin 
var cnt = 0;

var circles = new Array(100);

var counter = 0;
var cCounter = 0;
var cTimer = 0;

function setup() {
  createCanvas(500, 500);
  frameRate(30);

  gaph = 3;
  gapv = 3;
  marginh = 20;
  marginv = 20;

  // field 
  field = new Array(int((width - marginh * 2) / gaph));
  for (var i = 0; i < field.length; i++) {
    field[i] = new Array(int((height - marginv * 2) / gapv));
  }
  fieldShade = new Array(field.length);
  for (var i = 0; i < fieldShade.length; i++) {
    fieldShade[i] = new Array(field[0].length);
  }

  for (var i = 0; i < field.length; i++) {
    for (var k = 0; k < field[0].length; k++) {
      field[i][k] = TWO_PI - PI / 3;
      fieldShade[i][k] = 1;
    }
  }
}

function draw() {
  background(50);

  var ax, ay;
  cTimer++;

  if (cTimer > 5 && cCounter < circles.length - 1) {
    circles[cCounter] = new Circle(250, 250, 40, cCounter);
    cCounter++;
    cTimer = 0;
  }

  var len = 10;

  stroke(255, 255, 255, 50);
  // Draw every other one for better frame rate
  for (var i = 0; i < field.length; i += 2) {  
    for (var k = 0; k < field[0].length; k += 2) {
      ax = marginh + i * gaph;
      ay = marginv + k * gapv;
      line(ax, ay, ax + len * cos(field[i][k]), ay + len * sin(field[i][k]));
    }
  }

  noStroke();
  noFill();
  for (var i = 0; i < cCounter; i++) {
    circles[i].draw();
    circles[i].getGrid();
  }

}

function getLocation(i, k) {
  return [marginh + i * gaph, marginv + k * gapv];
}

function Circle(px, py, pr, id) {

  this.x = px;
  this.y = py;
  this.r = pr;
  this.rr = this.r * this.r;
  this.d = this.r * 2;
  this.d2 = this.d * this.d;

  this.id = id;

  this.sp1 = random(2);
  this.sp2 = random(2);
  this.sp3 = random(2);

  this.ac1 = random(0.5) - random(0.5);
  this.ac2 = random(0.5) - random(0.5);
  this.ac3 = random(0.5) - random(0.5);

  this.inx = 0;
  this.iny = 0;

  this.over = true;

  this.draw = function() {
    this.move();
  }

  this.move = function() {
    var angle = sin(this.sp1) - cos(this.sp2);

    this.sp1 += this.ac1;
    this.sp2 += this.ac2;
    this.sp3 += this.ac3;

    angle = (angle < 0) ? angle + TWO_PI : ((angle >= TWO_PI) ? angle - TWO_PI : angle);

    this.x += sin(angle);
    this.y -= cos(angle);

    this.checkBounds();
    this.checkOverlap();
  }

  this.checkBounds = function() {
    if (this.x > width) this.x = 0;
    if (this.x < 0) this.x = width;
    if (this.y > height) this.y = 0;
    if (this.y < 0) this.y = height;
  }

  this.repel = function(angle) {
    this.x = this.x + cos(angle) / 10.0;
    this.y = this.y + sin(angle) / 10.0;
  }

  this.setState = function(px, py) {
    this.inx = px;
    this.iny = py;
    this.over = true;
  }

  this.checkOverlap = function() {

    for (var i = this.id + 1; i < cCounter; i++) {

      var dx = circles[i].x - this.x;
      var dy = circles[i].y - this.y;
      var drr = dx * dx + dy * dy;
      var ds = sqrt(drr);

      if (ds > this.r + circles[i].r || ds < abs(this.r - circles[i].r)) {
        continue; // no solution 
      }

      var ang = atan2(dy, dx);
      this.repel(ang + PI);
      circles[i].repel(ang);

      var a = (this.rr - circles[i].rr + drr) / (2 * ds);
      var x2 = this.x + (a * (circles[i].x - this.x) / ds);
      var y2 = this.y + (a * (circles[i].y - this.y) / ds);

      this.setState(x2, y2);
      circles[i].setState(x2, y2);
    }
  }

  this.getGrid = function() {

    var sx = Math.ceil((this.x - this.r - marginh) / gaph);
    var sy = Math.ceil((this.y - this.r - marginv) / gapv);

    var numx = Math.floor(this.d / gaph);
    var numy = Math.floor(this.d / gapv);

    var pos = [];

    for (var i = sx; i < sx + numx; i++) {
      if (i >= 0 && i < field.length) {
        for (var k = sy; k < sy + numy; k++) {
          if (k >= 0 && k < field[0].length) {
            if (this.over) {
              pos = getLocation(i, k);
              var dx = pos[0] - this.x;
              var dy = pos[1] - this.y;
              if (dist(this.x, this.y, pos[0], pos[1]) < this.r) {
                var da = atan2(pos[1] - this.iny, pos[0] - this.inx);
                if (field[i][k] < da) {
                  field[i][k] += PI / 20;
                } else if (field[i][k] > da) {
                  field[i][k] -= PI / 20;
                }
                fieldShade[i][k] = 2;
              }
            }
          }
        }
      }
    }
    this.over = false;
  }

}