Return to menu
/*

   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

   Implemented by William Ngan <http://metaphorical.net>
   4 April 2004
   Processing v.68 <http://processing.org>

*/

Circle[] cc;

void setup()
{
  size( 600, 600 );
  framerate( 30 );
  cc = new Circle[100];
  for (int i=0; i<cc.length; i++) {
    cc[i] = new Circle( random(width), random(height), 15+random(20), i );
  }
  ellipseMode(CENTER_DIAMETER);
  rectMode(CENTER_DIAMETER);
  noFill();
}

void loop()
{
  background(255);

  noStroke();
  fill(0);

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

  noFill();
  stroke(255);

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

class Circle {

  float x, y, r, r2, d, d2;
  float lastx, lasty;

  float ac1, ac2, ac3;
  float sp1, sp2, sp3;

  float inx = 0;
  float iny = 0;

  boolean hasIntersect = false;
  Hair[] hairs;

  int gray;

  int id;

  Circle( float px, float py, float pr, int id ) {
    x = px;
    y = py;
    r = pr;
    r2 = r*r;
    d = r*2;
    d2 = d*d;

    this.id = id;

    gray = 0;

    hairs = new Hair[30];
    float stepA =  2*PI/hairs.length;;
    for (int i=0; i<hairs.length; i++) {

      hairs[i] = new Hair( cos(stepA*i)*r, sin(stepA*i)*r, 5, stepA*i+PI, this );
    }

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

    ac1 = random(0.5)-random(0.5);
    ac2 = random(0.5)-random(0.5);
    ac3 = random(0.5)-random(0.5);
  }

  void draw() {
    ellipse(x, y, d, d);
    move();
  }

  void drawHair() {

    for (int i=0; i<hairs.length; i++) {
      hairs[i].updatePos();
      hairs[i].draw();
    }
  }

  void move() {
    float angle = sin( sp1 ) - cos(sp2);

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

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

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

    checkBounds();

    checkIntersect();

  }

  void checkIntersect() {

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

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

    hasIntersect = flag2;

  }

  boolean intersect( Circle cB ) {

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

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

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

    float paX = x2 + h*(cB.y - y)/d;
    float paY = y2 - h*(cB.x - x)/d;
    //float pbX = x2 - h*(cB.y - y)/d;
    //float pbY = y2 + h*(cB.x - x)/d;

    repel( atan2( dy, dx) );

    ellipse( paX, paY, 15, 15 );
    //ellipse( pbX, pbY, 20, 20 );

    //if ( cB.id > id ) {
      inx = x2;
      iny = y2;

    //} else {
      //inx = pbX;
      //iny = pbY;
    //}

    return true;

  }

  void repel( float angle ) {
    x = x + cos(angle)/4;
    y = y + sin(angle)/4;

  }

  void hairFocus( float px, float py) {

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

  }

  void hairFocus() {
    for (int i=0; i<hairs.length; i++) {
      hairs[i].revertFocus();
    }
  }

  void checkBounds() {
    //x = Math.min( width, Math.max( 0, x ) );
    //y = Math.min( height, Math.max( 0, y ) );

    if ( x > width ) x = 0;
    if ( x < 0 ) x = width;
    if ( y > height ) y = 0;
    if ( y < 0 ) y = height;

  }

}

class Hair {

  float repel_speed;

  float radius, origRadius;
  float angle, origAngle;
  float x, y, regX, regY, nextX, nextY, lastx, lasty;

  float speedFactor = 5;

  Circle parent;

  Hair( float rx, float ry, float r, float a, Circle parent ) {
    radius = r;
    origRadius = r;

    angle = a;
    origAngle = a;

    this.parent = parent;

    regX = rx;
    regY = ry;

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

    x = nextX;
    y = nextY;

  }

  void updatePos() {

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

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

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

  }

  void draw() {
    line( parent.x+regX, parent.y+regY, x, y );

  }

  void focus( float px, float py ) {
    float dx = px-(parent.x+regX);
    float dy = py-(parent.y+regY);

    angle = atan2( dy, dx );
    radius = dist( px, py, parent.x+regX, parent.y+regY );
  }

  void revertFocus() {
    angle = origAngle;
    radius = origRadius;
  }
}