> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.sYSJYcmXlGS/rev.1237
 * 
 * authors: 
 *   Ralegh Austin

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




class Rectangle {
  // top left corner
  float x, y, w, h;

  Rectangle(float _x, float _y, float _w, float _h) {
    x = _x;
    y = _y;
    w = _w;
    h = _h;
  }

  void show() {
    rect(x, y, w, h);
  }

  boolean containsPoint(Point p) {
    return !(p.x < x || p.x > x + w || p.y < y || p.y > y + h);
  }

  boolean intersects(Rectangle other) {
    return !(y + h <= other.y || y >= other.y + other.h || x + w <= other.x || x >= other.x + other.w);
  }
}


class Quadtree {
  Quadtree nw, ne, sw, se;
  Atom[] atoms;
  Rectangle r;
  int cap, count;
  boolean split = false;

  Quadtree(Rectangle _r, int _cap) {
    count = 0;
    cap = _cap;
    r = _r;
    atoms = new Atom[cap];
  }

  void show() {
    r.show();
    if (split) {
      nw.show();
      ne.show();
      sw.show();
      se.show();
    }
  }

  ArrayList<Atom> query(Rectangle area) {
    if (area.intersects(r)) {
      ArrayList<Atom> tmp = new ArrayList<Atom>();
      for (int i = 0; i < count; i++) {
        tmp.add(atoms[i]);
      }
      if (split) {
        tmp.addAll(nw.query(area)); 
        tmp.addAll(ne.query(area)); 
        tmp.addAll(sw.query(area)); 
        tmp.addAll(se.query(area));
      }
      return tmp;
    } else {
      return new ArrayList<Atom>();
    }
  }

  boolean addAtom(Atom a) {
    if (count >= cap) {
      if (split) {
        if (nw.addAtom(a))
          return true;
        if (ne.addAtom(a))
          return true;
        if (sw.addAtom(a))
          return true;
        if (se.addAtom(a))
          return true;
      } else {
        nw = new Quadtree(new Rectangle(r.x, r.y, r.w / 2, r.h / 2), cap);
        ne = new Quadtree(new Rectangle(r.x + r.w / 2, r.y, r.w / 2, r.h / 2), cap);
        sw = new Quadtree(new Rectangle(r.x, r.y + r.h / 2, r.w / 2, r.h / 2), cap);
        se = new Quadtree(new Rectangle(r.x + r.w / 2, r.y + r.h / 2, r.w / 2, r.h / 2), cap);
        split = true;
        this.addAtom(a);
      }
    } else {
      if (r.containsPoint(a.p)) {
        atoms[count++] = a;
        return true;
      }
    }
    return false;
  }
}


class Point {
  float x, y;

  float distance(Point other) {
    return dist(x, y, other.x, other.y);
  }

  Point(float _x, float _y) {
    x = _x;
    y = _y;
  }
}


class Atom {
  Point p;
  float r, m, xVel, yVel, nextXVel, nextYVel;
  int red, green, blue;

  Atom(Point _p, float _r, float _m, int _red, int _green, int _blue) {
    p = _p;
    r = _r;
    m = _m;
    xVel = 0;
    yVel = 0;
    red = _red
    green = _green;
    blue = _blue;
  }

  Atom(float x, float y, float _r, float _m, int _red, int _green, int _blue) {
    p = new Point(x, y);
    r = _r;
    m = _m;
    xVel = 0;
    yVel = 0;
    red = _red
    green = _green;
    blue = _blue;
  }

  Rectangle queryRect(float maxR) {
    return new Rectangle(p.x - r - maxR, p.y - r - maxR, 2 * (r + maxR) , 2 * (r + maxR));
  }

  boolean intersects(Atom other) {
    return r + other.r >= p.distance(other.p);
  }

  void update() {
    xVel = nextXVel;
    yVel = nextYVel;
    p.x += xVel;
    p.y += yVel;
    if(p.x - r <= 0) {
      xVel = abs(xVel);   
    } else if(p.x + r >= width) {
      xVel = -abs(xVel);  
    }
    if(p.y - r <= 0) {
      yVel = abs(yVel);   
    } else if(p.y + r >= height) {
      yVel = -abs(yVel);  
    }
    
    xVel += random(-1, 1);
    yVel += random(-1, 1);
    
    if (abs(xVel) > 0.01) {
        xVel -= (xVel * r * 0.02);
    }
    if (abs(yVel) > 0.01) {
        yVel -= (yVel * r * 0.02);
    }
    /*
    if (xVel > 0) {
      xVel -= r * 0.01;
    } else if (xVel < 0) {
      xVel += r * 0.01;
    }
    if (yVel > 0) {
      yVel -= r * 0.01;
    } else if (yVel < 0) {
      yVel += r * 0.01;
    }
    */
    nextXVel = xVel;
    nextYVel = yVel;
  }

  void show() {
    fill(red, green, blue);
    stroke(red, green, blue);
    ellipse(p.x, p.y, 2 * r, 2 * r);
  }

  void collide(Atom other) {
    //float startVel = vel;
    //float otherStartVel = other.vel;
    //float a = startVel * m;  
    //float b = otherStartVel * other.m;
    //float theta = abs(velAngle - other.velAngle);
    //float total = sqrt(sq((a * sin(theta) + b)) + sq(a * cos(theta)));
    //float newAngle = 

    //float sXV = xVel, sYV = yVel, oSXV = other.xVel, oSYV = other.yVel;
    overlap = 2 * pow(1 - (p.distance(other.p) / (r + other.r)), 0.5)
    nextXVel = (xVel * (m - other.m) + (2 * other.m * other.xVel)) / (m + other.m) + overlap * (abs(p.x - other.p.x) / (p.x - other.p.x));
    nextYVel = (yVel * (m - other.m) + (2 * other.m * other.yVel)) / (m + other.m) + overlap * (abs(p.y - other.p.y) / (p.y - other.p.y));
    //other.nextXVel = (other.xVel * (other.m - m) + (2 * m * xVel)) / (other.m + m) + overlap * (abs(other.p.x - p.x) / (other.p.x - p.x));
    //other.nextYVel = (other.yVel * (other.m - m) + (2 * m * yVel)) / (other.m + m) + overlap * (abs(other.p.y - p.y) / (other.p.y - p.y));
  }
}


Quadtree q;
ArrayList<Atom> atoms = new ArrayList<Atom>();
float maxRadius;
boolean paused = false;

void setup() {
  size(640, 400);
  stroke(255); 
  maxRadius = 5;
  for (int i = 0; i < 100; i++) {
    atoms.add(new Atom(random(0, width), random(0, height), random(5, 10), random(2, 3), 255, 0, 0));
  }
}

void mouseClicked() {
  atoms.add(new Atom(mouseX, mouseY, 50, 200, 0, 0, 255));
}

void mouseDragged() {
  for (int i = 0; i < 10; i++) {
    atoms.add(new Atom(mouseX + random(-5, 5), mouseY + random(-5, 5), random(5, 10), random(2, 3), 0, 0, 255));
  }
}

void draw() {
  //println(frameRate);
  background(51);
  fill(255);
  q = new Quadtree(new Rectangle(0, 0, width, height), 1);
  for (int i = 0; i < atoms.size(); i++) {
    q.addAtom(atoms.get(i));
  }

  for (int i = 0; i < atoms.size(); i++) {
    atoms.get(i).update();
    ArrayList<Atom> tmp = q.query(atoms.get(i).queryRect(maxRadius));
    for (int j = 0; j < tmp.size(); j++) {
      if (atoms.get(i) != tmp.get(j) && atoms.get(i).intersects(tmp.get(j))) {
        atoms.get(i).collide(tmp.get(j));
      }
    }
    atoms.get(i).show();
  }
  noFill();
  stroke(255);
  //q.show();
}

void keyPressed() {
    if(keyCode == ' ') {
        if(paused) {
            frameRate(0);
        } else {
            frameRate(1);   
        }
        paused = !paused;
    }
}