> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.UlB8vsGzdgs/rev.1308
 * 
 * authors: 
 *   
 *   darksquid

 * license (unless otherwise specified): 
 *   creative commons attribution-share alike 3.0 license.
 *   https://creativecommons.org/licenses/by-sa/3.0/ 
 */ 



//Copyright 2012 Zak Hammerman. Free to distribute and edit noncommercially, with proper attribution.
//A simple sketch showing connections between moving bodies or nodes.
//The cursor location determines opacity; clicking toggles circle visibility.
//Keys: 'n' creates a new set of values
//      '=' adds a ball, '-' removes one randomly <--Disabled for now.
//      'o' toggles current quantity of objects 
//      'f' toggles average framerate
//      'v' toggles average velocities (x,y) *off by default*
//Each ball's size is in an inverse proportion to its speed. (1/radius)
//This code has not been optimised yet. It's more of a WIP.
//Version 0.2: converted the array structure into an ArrayList to freely manipulate the amount of objects.
//Version 0.3: added more detailed info, overhauled the formatting and added simple error checking for IndexOutOfBoundsExceptions for the ArrayList. Still working on desktop but not ProcessingJS.
//        0.3.1: circles are relatively opaque on mouseover; lines between circles are pink when two circles intersect.
//Version 0.4: returned to the more reliable array structure. Cleaned up some code. The previous changes should be visible now. Woohoo!
//Verdion 0.4.1: a little cleanup.

int index=(int)random(20, 30);
boolean filled, showobj=true, showfrate=true, showvel=false;
Ball[] balls;
void setup() {
  size(600, 600);
  frameRate(60);
  smooth();
  background(15);
  fill(255);
  filled=true;
  balls = new Ball[index];
  for(int i=0;i<index;i++) balls[i] = new Ball(random(25,60),filled);
}
void draw() {
  background(15);
  for (int i=0;i<balls.length;i++) balls[i].update();
  for (int x=0;x<balls.length;x++)
    for (int y=x; y<balls.length; y++) {
      if (balls[x].l.dist(balls[y].l) <=200) {
        strokeWeight(1.5);
        if (balls[x].l.dist(balls[y].l)<=(balls[x].r/2)+(balls[y].r/2)) {
          stroke(200, 50, 50, 127);
        }
        else stroke(100, 100, 100, 127);
        line(balls[x].l.x, balls[x].l.y, balls[y].l.x, balls[y].l.y);
      }
    }
  fill(255);
  if (showobj) text("Objects: " + balls.length, 5, height-40);
  if (showfrate) text("Framerate: " + round(frameRate), 5, height-25);
  if (showvel) text("Average velocity: (" + avgVel().x + ", " + avgVel().y + ")", 5, height-10);
}
boolean intersect(Ball a, Ball b) {
  if (a.l.dist(b.l)<=(a.r/2)+(b.r/2)) return true;
  else return false;
}
void mousePressed() {       //Toggles visibility of circles
  if (filled) filled=false;
  else filled=true;
  for (int i=0;i<balls.length;i++) {
    balls[i].col=filled;
  }
}
void keyPressed() {        //Creates a new set of circles and values
  switch(key) {
  case 'n':
    for (int i=0;i<balls.length;i++) balls[i]=new Ball(random(25, 60), filled);
    break;
  /*case '=':    //No longer needed. The ArrayList is being used on the desktop version but not in ProcessingJS. It's rather uncooperative...
    balls.add(new Ball(random(20, 90), filled));
    break;
  case '-':
    try {
      balls.remove((int)random(0, balls.size()-1));
    } 
    catch (IndexOutOfBoundsException e) {
    }   //Don't throw a fit when balls.size()=0
    break;*/
  case 'f':
    if (showfrate) showfrate=false;
    else showfrate=true;
    break;
  case 'o':
    if (showobj) showobj=false;
    else showobj=true;
    break;
  case 'v':
    if (showvel) showvel=false;
    else showvel=true;
    break;
  }
}

PVector avgVel() {
  float tx=0, ty=0;
  for (int i=0; i<balls.length-1; i++) {
    tx+=abs(balls[i].v.x);
    ty+=abs(balls[i].v.y);
  }
  tx/=balls.length;
  ty/=balls.length;
  return new PVector(tx, ty, 0.0);
}
class Ball {

  private int c1, c2, c3;
  private PVector v, l, m;
  private float r;
  private color f, s;
  private boolean col, intersect;

  Ball(float rad,   boolean filled) {
    r=rad;
    v=new PVector(random(128*(1/r), 256*(1/r)), random(128*(1/r), 256*(1/r)), 0);
    l=new PVector(int(random(r/2,width-r/2)),int(random(r/2,height-r/2)), 0);
    m=new PVector(-width, -height);
    c1=int(random(25, 255));
    c2=int(random(25, 255));
    c3=int(random(25, 255));
    col=filled;
  }

  void update() {
    l.x += v.x;
    l.y += v.y;
    v=collision();
    this.display();
  }

  void display() {
    m.set(mouseX, mouseY, 0);
    strokeWeight(7.5);
    if(!col && dist(mouseX,mouseY,l.x,l.y)<r){
        stroke(255, 0, 0,225);
      }
     else stroke(100, 100, 100, 127);
    if(col) {
      if(dist(mouseX,mouseY,l.x,l.y)<r){
        f=color(c1, c2, c3,160);
        s=color(c1, c2, c3,225);
      }
      else f=color(c1, c2, c3, (4*width)/dist(l.x, l.y, m.x, m.y));
      s=f;
      fill(f);
      stroke(s);
      ellipse(l.x, l.y, r, r);
    }
    point(l.x, l.y);
  }
  PVector collision() {
    if (l.x < r/2 || l.x > width-r/2) {
      if (l.x < r/2) l.x=r/2;
      if (l.x > width-r/2) l.x=width-r/2;
      v.x *= -1;
      if (frameCount%2==0) {          //Psuedorandom changes in x-velocity
        v.x*=(1/v.x);
        v.x*=random(128*(1/r), 256*(1/r));
      }
    } 
    if (l.y < r/2 || l.y > height-r/2) {
      if (l.y < r/2) l.y=r/2;
      if (l.y > height-r/2) l.y=height-r/2;
      v.y *= -1;
      if (frameCount%2==0) {          //Same type of changes in y-velocity
        v.y*=(1/v.y);
        v.y*=random(128*(1/r), 256*(1/r));
      }
    } 
    return v;
  }
}