{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
   
   Note about the JavaScript port to p5.js:
   This software is now significantly different because
   the "glow" effects created by Tarbell worked well as
   in a Java Applet in 2004, but this same process made
   the code too slow in JavaScript in 2016. Early Processing 
   from 2004 was quick with pixel operations. 
 
   Restored by Casey Reas <http://reas.com> 
   22 June 2016 
   Processing v.3.1.1 <http://processing.org> 
   
   Implemented by J. Tarbell <http://levitated.net> 
   8 April 2004 
   Processing v.68 <http://processing.org> 
 
*/

var num = 100;
var time = 0;

// object array 
var discs = [];

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

  // make discs, arrange in anti-collapsing circle 
  for (var i = 0; i < num; i++) {
    var fx = 0.4 * width * cos(TWO_PI * i / num);
    var fy = 0.4 * width * sin(TWO_PI * i / num);
    var x = random(width / 2) + fx;
    var y = random(width / 2) + fy;
    var r = 5 + random(45);
    var bt = 1;
    if (random(100) < 50) {
      bt = -1;
    }
    discs[i] = new Disc(i, x, y, bt * fx / 1000.0, bt * fy / 1000.0, r);
    discs[i].createPixelRiders();
  }
}

// main 
function draw() {
  background(0);
  loadPixels();  // Added CR -- 13 July 2016
  // move discs 
  for (var c = 0; c < num; c++) {
    discs[c].move();
    discs[c].render();
    discs[c].renderPxRiders();
  }
  time++;
}

// disc object 
function Disc(Id, X, Y, Vx, Vy, R) {

  this.id = Id;
  this.x = X;
  this.y = Y;
  this.vx = Vx;
  this.vy = Vy;
  this.dr = R;
  this.r = 0;

  this.numr = 0;
  this.maxr = 40;
  this.pxRiders = [];

  this.createPixelRiders = function() {
    this.numr = int(R / 1.62);
    if (this.numr > this.maxr) {
      this.numr = this.maxr;
    }

    // create pixel riders 
    for (var n = 0; n < this.maxr; n++) {
      this.pxRiders[n] = new PxRider();
    }
  }

  this.draw = function() {
    stroke(0, 50);
    noFill();
    ellipse(this.x, this.y, this.r, this.r);
  }

  this.render = function() {
    // find intersecting points with all ascending discs 
    for (var n = this.id + 1; n < num; n++) {
      if (n != this.id) {
        // find distance to other disc 
        var dx = discs[n].x - this.x;
        var dy = discs[n].y - this.y;
        var d = sqrt(dx * dx + dy * dy);
        // intersection test 
        if (d < (discs[n].r + this.r)) {
          // complete containment test 
          if (d > abs(discs[n].r - this.r)) {
            // find solutions 
            var a = (this.r * this.r - discs[n].r * discs[n].r + d * d) / (2 * d);

            var p2x = this.x + a * (discs[n].x - this.x) / d;
            var p2y = this.y + a * (discs[n].y - this.y) / d;

            var h = sqrt(this.r * this.r - a * a);

            var p3ax = p2x + h * (discs[n].y - this.y) / d;
            var p3ay = p2y - h * (discs[n].x - this.x) / d;

            var p3bx = p2x - h * (discs[n].y - this.y) / d;
            var p3by = p2y + h * (discs[n].x - this.x) / d;

            // P3a and P3B may be identical - ignore this case (for now) 
            stroke(255, 204); // Modified CR -- 13 July 2016
            strokeWeight(4); // Modified CR -- 13 July 2016
            point(p3ax, p3ay);
            point(p3bx, p3by);
          }
        }
      }
    }
  }

  this.move = function() {
    // add velocity to position 
    this.x += this.vx;
    this.y += this.vy;
    // bound check 
    if (this.x + this.r < 0) this.x += width + this.r + this.r;
    if (this.x - this.r > width) this.x -= width + this.r + this.r;
    if (this.y + this.r < 0) this.y += width + this.r + this.r;
    if (this.y - this.r > width) this.y -= width + this.r + this.r;

    // increase to destination radius 
    if (this.r < this.dr) {
      this.r += 0.1;
    }
  }

  this.renderPxRiders = function() {
    for (var n = 0; n < this.numr; n++) {
      this.pxRiders[n].move(this.x, this.y, this.r);
    }
  }
}

// pixel rider object  
function PxRider() {

  this.t = random(TWO_PI);
  this.vt = 0.0;
  this.mycharge = 0.0;

  this.move = function(x, y, r) {
    strokeWeight(1);

    // add velocity to theta 
    this.t = (this.t + this.vt + PI) % TWO_PI - PI;
    this.vt += random(-0.001, 0.001);

    // apply friction brakes 
    if (abs(this.vt) > 0.02) this.vt *= 0.9;

    // draw      
    var px = int(x + r * cos(this.t));
    var py = int(y + r * sin(this.t));
    //var c = get(int(px), int(py));  // Removed CR -- 13 July 2016
    var c = pixels[py * height + px];  // Added CR -- 13 July 2016
    //if (brightness(c) > 48) {  // Removed CR -- 13 July 2016
    if (int(c) > 48) {  // Modified CR -- 13 July 2016
      //glowpoint(px, py);  // Removed CR -- 13 July 2016
      this.mycharge = 164;
    } else {
      stroke(this.mycharge);
      point(px, py);
      this.mycharge *= 0.98;
    }

  }
}

// methods 
function glowpoint(px, py) { // Too slow in browser 13 July 2016
  for (var i = -2; i < 3; i++) {
    for (var j = -2; j < 3; j++) {
      var a = 0.8 - i * i * 0.1 - j * j * 0.1;
      tpoint(int(px + i), int(py + j), '#FFFFFF', a);
    }
  }
}

function tpoint(x1, y1, mycolor, a) {
  // place translucent point 
  var r, g, b;
  var c;
  var myc = color(mycolor);
  c = get(x1, y1);
  r = int(red(c) + (red(myc) - red(c)) * a);
  g = int(green(c) + (green(myc) - green(c)) * a);
  b = int(blue(c) + (blue(myc) - blue(c)) * a);
  var nc = color(r, g, b);
  stroke(nc);
  stroke(255, 204, 0);
  point(x1, y1);
}