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