{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 cc = [];
var numCircles = 100;

function setup() {
  createCanvas(600, 600);
  frameRate(30);
  for (var i = 0; i < numCircles; i++) {
    cc[i] = new Circle(random(width), random(height), 15 + random(20), i);
    cc[i].makeHairs();
  }
}

function draw() {
  background(255);

  noStroke();
  fill(0);

  for (var i = 0; i < cc.length; i++) {
    cc[i].draw();
  }

  noFill();
  stroke(255);

  for (var i = 0; i < cc.length; i++) {
    cc[i].drawHair();
  }
}

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

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

  this.id = id;

  this.gray = 0;

  this.hairs = [];
  this.numHairs = 30;

  this.stepA = 2 * PI / this.numHairs;

  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.hasIntersect = false;

  this.makeHairs = function() {
    for (var i = 0; i < this.numHairs; i++) {
      this.hairs[i] = new Hair(cos(this.stepA * i) * this.r,
        sin(this.stepA * i) * this.r, 5, this.stepA * i + PI, cc[this.id]);
    }
  }

  this.draw = function() {
    fill(0);
    noStroke();
    ellipse(this.x, this.y, this.d, this.d);
    this.move();
  }

  this.drawHair = function() {
    for (var i = 0; i < this.hairs.length; i++) {
      this.hairs[i].updatePos();
      this.hairs[i].draw();
    }
  }
  
  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.checkIntersect();
  }

  this.checkIntersect = function() {

    var flag = false;
    var flag2 = false;
    for (var i = 0; i < cc.length; i++) {
      if (i != this.id) {
        flag = this.intersect(cc[i]);
        if (!flag2) {
          flag2 = flag;
        }
      }
    }

    if (flag2) {
      this.hairFocus(this.inx, this.iny);
    } else if (this.hasIntersect) {
      this.hairFocusRevert();
    }

    this.hasIntersect = flag2;

  }

  this.intersect = function(cB) {

    var dx = this.x - cB.x;
    var dy = this.y - cB.y;
    var d2 = dx * dx + dy * dy;
    var d = sqrt(d2);

    if (d > this.r + cB.r || d < abs(this.r - cB.r)) return false;

    var a = (this.r2 - cB.r2 + d2) / (2 * d);
    var h = sqrt(this.r2 - a * a);
    var x2 = this.x + a * (cB.x - this.x) / d;
    var y2 = this.y + a * (cB.y - this.y) / d;

    var paX = x2 + h * (cB.y - this.y) / d;
    var paY = y2 - h * (cB.x - this.x) / d;

    this.repel(atan2(dy, dx));

    fill(0);
    ellipse(paX, paY, 15, 15);

    this.inx = x2;
    this.iny = y2;

    return true;
  }

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

  this.hairFocus = function(px, py) {
    for (var i = 0; i < this.hairs.length; i++) {
      this.hairs[i].focus(px, py);
    }
  }

  this.hairFocusRevert = function() {
    for (var i = 0; i < this.hairs.length; i++) {
      this.hairs[i].revertFocus();
    }
  }

  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;
  }

}

function Hair(rx, ry, r, a, parentCircle) {

  this.regX = rx;
  this.regY = ry;

  this.radius = r;
  this.origRadius = r;

  this.angle = a;
  this.origAngle = a;

  this.pc = parentCircle;  // Parent circle

  this.nextX = this.pc.x + this.regX + cos(this.angle) * this.radius;
  this.nextY = this.pc.y + this.regY + sin(this.angle) * this.radius;

  this.x = this.nextX;
  this.y = this.nextY;

  this.speedFactor = 5;

  this.updatePos = function() {

    this.nextX = this.pc.x + this.regX + cos(this.angle) * this.radius;
    this.nextY = this.pc.y + this.regY + sin(this.angle) * this.radius;

    var dx = this.nextX - this.x;
    var dy = this.nextY - this.y;
    
    if (abs(dx) > 1) {
      this.x += dx / this.speedFactor;
      this.y += dy / this.speedFactor;
    }

    if (abs(dx) > 200 || abs(dy) > 200) {
      this.x = this.nextX;
      this.y = this.nextY;
    }

  }

  this.draw = function() {
    stroke(255);
    line(this.pc.x+this.regX, this.pc.y+this.regY, this.x, this.y);
  }

  this.focus = function(px, py) {
    var dx = px - (this.pc.x + this.regX);
    var dy = py - (this.pc.y + this.regY);
    this.angle = atan2(dy, dx);
    this.radius = dist(px, py, this.pc.x+this.regX, this.pc.y+this.regY);
  }

  this.revertFocus = function() {
    this.angle = this.origAngle;
    this.radius = this.origRadius;
  }
}