//Click the left and right mouse buttons to grow and shrink the objects, respectively
/*A large field of colliding circles. The centers are shown as a dot.
a transparent ring surrounds each dot, it shows the extends of the current
radius, and also as a minor eraser on the board. Clicking the mouse increases
and decreases it's size. When two circles strike each other, a line is drawn
between their centers that has it's brightness and shade bound to the
intensity of the collision.
*/
Ball[] wombat; //named wombat because I like the name wombat, and it is easy to remember
int ballCount = 100; //number of shapes set by assignment
float colStr = 0; //Global tossed by collisions to know how hard the most recent was
float rad = 15; //Global radius because all are the same... makes updating cheaper
void setup(){
size(400,400);
background(0);
colorMode(HSB);
smooth();
noFill();
wombat = new Ball[ballCount];
//Spread all 100 wombats about the field
for(int i = 0; i < ballCount; i++){
wombat[i] = new Ball(random(0, width), random(0,height));
wombat[i].spur(); //Give them an arbitrary direction
}
}
void draw(){
//Allow the user to see various forms created by increasing
//and decreasing the collision radius
if (mousePressed){
if (mouseButton == LEFT) {
rad += .5;
rad = constrain(2, rad, 30);
} else if (mouseButton == RIGHT) {
rad -= .5;
rad = constrain(2, rad, 30);
}
}
//Move the coords to the bottom left for easier to remember math.
translate(0,height);
scale(1,-1);
//Update all the womabt positions
for(int i = 0; i < ballCount; i++){
wombat[i].update();
}
//Now see if any of them hit each other
for(int i = 0; i < ballCount; i++){
//Only the one's we haven't already checked
for(int j = i+1; j < ballCount; j++){
if (collide(wombat[i], wombat[j])){
wombat[i].update(); //update them again now that they hit
wombat[j].update();
//Draw the line between them
strokeWeight(2);
//Base the hue and brightness on the force
stroke (130+colStr*20, 180, colStr*255, 200);
line(wombat[i].pos, wombat[j].pos);
}
}
}
}
//Overload line() to make it easier to read when I want to draw between two points;
void line(Vector A, Vector B){
line(A.x, A.y, B.x, B.y);
}
class Ball{
Vector pos, vel; //One XY for where we are, and another for how much we want to move
Ball(float X, float Y){
pos = new Vector(X, Y);
vel = new Vector();
}
//Check for the edges and bounce back so everything stays inside
void wallCheck(){
if( (0 > pos.x) || (width < pos.x)){
vel.x = -vel.x;
pos.x = constrain(pos.x, 0, width);
}
if((0 > pos.y) || (height < pos.y)){
vel.y = -vel.y;
pos.y = constrain(pos.y, 0, height);
}
}
//General movement case
void run(){
wallCheck();
pos.add(vel);
//vel.mult(.99); //Remove ground friction so there is always movement
noStroke();
}
void update(){
run();
//Draw a dark circle to show the radius
stroke(0,20);
ellipse(pos.x, pos.y, rad*2, rad*2);
stroke(255,20);
//draw a white dot to see the center
point(pos.x, pos.y);
}
//For if we need to kick a ball randomly (such as starting out)
void spur(){
vel.add(new Vector(random(-.5, .5), random(-.5, .5)));
}
}
//Large collision function that does 3 main things.
//1: It returns true if there was a collision so at the global level,
// we know what happened, and can do simple if's.
//2: resolves and updates two colliding objects
//3: updates the global colStr telling us how strong the last hit was
boolean collide(Ball A, Ball B){
float disX = A.pos.x - B.pos.x;
float disY = A.pos.y - B.pos.y;
float disR = rad + rad;
if(disR*disR > (disX*disX + disY*disY)){ //Cheapest collision check we can do, because this is run often
Vector U1x, U1y, U2x, U2y; //Temp component Vectors
Vector temp = new Vector(disX, disY); //Axis Vector that we project on
//While this is a near mimic of the above if, this is more costly.
//As such, we only run it when we know there is a collision.
float over = (disR - sqrt(disX*disX + disY*disY))*.5; //Find the overlap
temp = temp.unit(); // Unit vector.
A.pos.add(temp.prod(over)); // Push A along the axis to outside of B.
//Save out aDot, to be returned later
float aDot = temp.dot(A.vel);
U1x = temp.prod(aDot); // Projections
U1y = A.vel.dif(U1x);
temp.mult(-1); // Flip the direction of the axis
B.pos.add(temp.prod(over)); // Push B along the axis to outside of A.
//Save out bDot
float bDot = temp.dot(B.vel);
U2x = temp.prod(bDot); //B projections
U2y = B.vel.dif(U2x);
//Final Velocities.
A.vel.set(U2x); //swapped because collisions swap components along the axis
A.vel.add(U1y);
//A.vel.mult(.75); //Removed lost energy so everything keeps moving
B.vel = new Vector();
B.vel.set(U1x);
B.vel.add(U2y);
//B.vel.mult(.75);
//Abs the dot products then return, because they are scalars -1 to 1
colStr = abs(aDot)+abs(bDot); //Will yield 0-2;
return true;
} else {
//No collision
return false;
}
}
//Make it easy to carry around a set of two floats and perform vector math on them
//Makes the collision system easier to understand
class Vector{
float x, y;
Vector(){
x = 0;
y = 0;
}
Vector(Vector V){
x = V.x;
y = V.y;
}
Vector(float X, float Y){
x = X;
y = Y;
}
void set(Vector V){
x = V.x;
y = V.y;
}
void add(Vector V){
x += V.x;
y += V.y;
}
void add(float X, float Y){
x += X;
y += Y;
}
Vector sum(Vector V){
Vector temp = new Vector(x + V.x, y + V.y);
return temp;
}
void sub(Vector V){
x -= V.x;
y -= V.y;
}
Vector dif(Vector V){
Vector temp = new Vector(x - V.x, y - V.y);
return temp;
}
void mult(float T){
x *= T;
y *= T;
}
Vector prod(float T){
Vector temp = new Vector(x*T, y*T);
return temp;
}
float magni(){
float magnitude = sqrt(x*x+y*y);
return magnitude;
}
Vector unit(){
Vector temp = new Vector(x,y);
temp.mult(1/magni());
return temp;
}
void normalize(){
this.mult(1/magni());
}
float dot(Vector V){
float temp = x*V.x + y*V.y;
return temp;
}
}
Exercise 12: Use arrays and for structures to control the motion of one hundred shapes.