Multiple ball collision

I need some multiple ball collision for my thesis project, so I came out with this little program.

The code is based on Circle Collision with Swapping Velocities by Ira Greenberg: circlecollision.html, that at the same time is based on Keith Peter»s Solution in Foundation Actionscript Animation: Making Things Move!

First, here is a little sketch build in processing, and then the same code adapted to Openframeworks.

Here you can download the processing version:

And here the openframework»s one:

This is the processing version in action (applet: opens a new window)

I will have to solve some problems that I am having with some balls that don»t bounce and stay inside of other balls. Also I read somewhere that the distance function (that is a square root that comes from the Pythagorean Theorem), used to determine if the ball is bouncing other (if the distance between them is less that their radius), is pretty intense for processing.
I»ve heard on several places that one solution could be dividing the whole space in several spots and use the distance function only if both balls are in the same spot. I don»t know yet how to do it, but I will give it a try. If anybody has any idea about it is more than welcome.

And here is a video:

Multiple Ball Collision from berio on Vimeo.

Processing code:

_b_CircleCollision.pde:

 
ArrayList ballList;
PFont font;

void setup() {
size(900, 600);
smooth();
noStroke();

ballList = new ArrayList();
font = loadFont("HelveticaNeue-48.vlw");
textFont(font, 12);
}

void draw() {
background(51);
fill(204);
if(ballList.size()>0){

// Update balls
for(int i=0; i Ball ball = (Ball) ballList.get(i);
ball.update();
}
}

if(ballList.size()>1){

// Check if there is a collision between balls
for(int i=0; i Ball ball1 = (Ball) ballList.get(i);
for(int j=i+1; j Ball ball2 = (Ball) ballList.get(j);
ball1.checkObjectCollision(ball2);
}
}

// Check if the ball that we are creating is on top
// of others
Ball lastBall = (Ball) ballList.get(ballList.size()-1);
lastBall.checkCreation(ballList);
}
}

void mousePressed(){
Ball ball = new Ball(mouseX, mouseY, random(10, 60), 0, 0);
ballList.add(ball);
}

void mouseReleased(){
Ball ball = (Ball) ballList.get(ballList.size()-1);
if(ball.getKillMe()){
ballList.remove(ballList.size()-1);
}else{
ball.createBall();
}
}

Ball.pde:

 
class Ball{
float r, m;
PVector v; //Velocity
PVector p; //Position
boolean ballCreated = false;
boolean letCreateBall = true;
boolean killMe = false;
color colorBall;
color colorNoBall = #e85d3d;
color colorYesBall = #a2e83d;

// default constructor
Ball() {
p = new PVector();
v = new PVector();
}

Ball(float _x, float _y, float _r, float _vx, float _vy) {
p = new PVector(_x, _y);
v = new PVector(_vx, _vy);
r = _r;
m = r * .1;
colorBall = colorYesBall;
}

public void checkCreation(ArrayList _bList){

if(!ballCreated){
ArrayList ballList = _bList;

// Check if the ball is on top of others
for(int i=0; i Ball oBall = (Ball) ballList.get(i);
if(dist(p.x, p.y, oBall.p.x, oBall.p.y) < r + oBall.r){
colorBall = colorNoBall;
killMe = true;
break;
}else{
colorBall = colorYesBall;
killMe = false;
}
}

}
}

public void createBall(){
createVelocity();
createRadius();
colorBall = colorYesBall;
ballCreated = true;
}

private void createVelocity(){

// Create velocity based on distance and angle between mouse and ball
float dis = min(dist(p.x, p.y, mouseX, mouseY), 300);
float ang = atan2(p.x-mouseX, p.y-mouseY);
v.x = map(dis, 0, 300, 0, 10) * sin(ang);
v.y = map(dis, 0, 300, 0, 10) * cos(ang);
}

private void createRadius(){

// Create radius based on distance between mouse and ball
float dis = min(dist(p.x, p.y, mouseX, mouseY), 300);
r = map(dis, 0, 300, 100, 5);
m = r * .1;
}

void update(){

if(!ballCreated){
createRadius();
createVelocity();
drawLine();
drawCreationBall();
}else{
p.add(v);
checkBoundaryCollision();
draw();
}
}

private void checkBoundaryCollision(){
if (p.x > width-r) {
p.x = width-r;
v.x *= -1;
}
else if (p.x < r) {
p.x = r;
v.x *= -1;
}
else if (p.y > height-r) {
p.y = height-r;
v.y *= -1;
}
else if (p.y < r) {
p.y = r;
v.y *= -1;
}
}

void checkObjectCollision(Ball _b){

Ball oBall = _b;

if(ballCreated && oBall.ballCreated){
PVector bVect = new PVector();
bVect.x = oBall.p.x - p.x;
bVect.y = oBall.p.y - p.y;

float bVectMag = sqrt(bVect.x * bVect.x + bVect.y * bVect.y);

if (bVectMag < r + oBall.r){
float theta = atan2(bVect.y, bVect.x);
float sine = sin(theta);
float cosine = cos(theta);

Ball[] bTemp = {new Ball(), new Ball()};

bTemp[1].p.x = cosine * bVect.x + sine * bVect.y;
bTemp[1].p.y = cosine * bVect.y - sine * bVect.x;

// rotate Temporary velocities
PVector[] vTemp = {new PVector(), new PVector()};
vTemp[0].x = cosine * v.x + sine * v.y;
vTemp[0].y = cosine * v.y - sine * v.x;
vTemp[1].x = cosine * oBall.v.x + sine * oBall.v.y;
vTemp[1].y = cosine * oBall.v.y - sine * oBall.v.x;

PVector[] vFinal = {new PVector(), new PVector()};
// final rotated velocity for b[0]
vFinal[0].x = ((m - oBall.m) * vTemp[0].x + 2 * oBall.m * vTemp[1].x) / (m + oBall.m);
vFinal[0].y = vTemp[0].y;
// final rotated velocity for b[0]
vFinal[1].x = ((oBall.m - m) * vTemp[1].x + 2 * m * vTemp[0].x) / (m + oBall.m);
vFinal[1].y = vTemp[1].y;

// hack to avoid clumping
bTemp[0].p.x += vFinal[0].x;
bTemp[1].p.x += vFinal[1].x;

// rotate balls
Ball[] bFinal = {new Ball(), new Ball()};
bFinal[0].p.x = cosine * bTemp[0].p.x - sine * bTemp[0].p.y;
bFinal[0].p.y = cosine * bTemp[0].p.y + sine * bTemp[0].p.x;
bFinal[1].p.x = cosine * bTemp[1].p.x - sine * bTemp[1].p.y;
bFinal[1].p.y = cosine * bTemp[1].p.y + sine * bTemp[1].p.x;

// update balls to screen position
oBall.p.x = p.x + bFinal[1].p.x;
oBall.p.y = p.y + bFinal[1].p.y;
p.x = p.x + bFinal[0].p.x;
p.y = p.y + bFinal[0].p.y;

// update velocities
v.x = cosine * vFinal[0].x - sine * vFinal[0].y;
v.y = cosine * vFinal[0].y + sine * vFinal[0].x;
oBall.v.x = cosine * vFinal[1].x - sine * vFinal[1].y;
oBall.v.y = cosine * vFinal[1].y + sine * vFinal[1].x;
}
}
}

public boolean getKillMe(){
return killMe;
}

private void drawLine(){
stroke(colorBall, 100);
line(p.x, p.y, mouseX, mouseY);
}

private void drawCreationBall(){
noStroke();
fill(colorBall, 255);
ellipse(p.x, p.y, 5, 5);
fill(colorBall, 100);
ellipse(p.x, p.y, r*2, r*2);

fill(colorBall, 255);
text("mass: " + m, p.x+r+12, p.y-12);
text("vel.x: " + v.x, p.x+r+12, p.y+3);
text("vel.y: " + v.y, p.x+r+12, p.y+18);
}

private void draw(){
fill(colorBall);
ellipse(p.x, p.y, r*2, r*2);
}
}