2D Collision in Canvas - Balls Overlapping When Velocity is High

I am doing a simple experiment in canvas using Javascript in which some balls will be thrown on the screen with some initial velocity and then they will bounce on colliding with each other or with the walls.

I managed to do the collision with walls perfectly but now the problem is with the collision with other balls. I am using the following code for it:

//Check collision between two bodies
function collides(b1, b2) {
//Find the distance between their mid-points
var dx = b1.x - b2.x,
dy = b1.y - b2.y,
dist = Math.round(Math.sqrt(dx*dx + dy*dy));

//Check if it is a collision
if(dist <= (b1.r + b2.r)) {
//Calculate the angles
var angle = Math.atan2(dy, dx),
sin = Math.sin(angle),
cos = Math.cos(angle);

//Calculate the old velocity components
var v1x = b1.vx * cos,
v2x = b2.vx * cos,
v1y = b1.vy * sin,
v2y = b2.vy * sin;

//Calculate the new velocity components
var vel1x = ((b1.m - b2.m) / (b1.m + b2.m)) * v1x + (2 * b2.m / (b1.m + b2.m)) * v2x,
vel2x = (2 * b1.m / (b1.m + b2.m)) * v1x + ((b2.m - b1.m) / (b2.m + b1.m)) * v2x,
vel1y = v1y,
vel2y = v2y;

//Set the new velocities
b1.vx = vel1x;
b2.vx = vel2x;
b1.vy = vel1y;
b2.vy = vel2y;
}
}


You can see the experiment here. The problem is, some balls overlap each other and stick together while some of them rebound perfectly. I don't know what is causing this issue. Here's my balls object if that matters:

function Ball() {
//Random Positions
this.x = 50 + Math.random() * W;
this.y = 50 + Math.random() * H;

this.r = 15 + Math.random() * 30;
this.m = this.r;

//Random velocity components
this.vx = 1 + Math.random() * 4;
this.vy = 1 + Math.random() * 4;

this.c = Math.round(Math.random() * 200);

this.draw = function() {
ctx.beginPath();
ctx.fillStyle = "rgb(" + this.c + ", " + this.c + ", " + this.c + ")";
ctx.arc(this.x, this.y, this.r, 0, Math.PI*2, false);
ctx.fill();
ctx.closePath();
}
}

• You don't need to use the square root to calculate if they are colliding. It's very inefficient, just do this: if (dx*dx + dy*dy >= (r1 + r2)*(r1 + r2)). – jcora Jul 8 '12 at 13:27
• @Bane this didn't changed anything. Also, condition should be <= not >=. Thanks anyway – Kushagra Jul 8 '12 at 13:35

I was working on something similar, in which balls will be colliding and rebounding at different angles and velocities. I found the solution in a book for ActionScript animation. Explaining it will take a lot of time and effort and honestly I cant really explain this since I've myself forgotten how most of this works. Please try your best to understand what this code is doing and if you need further assistance, ask me and I'll try my best to explain.

function rotate(x, y, sin, cos, reverse) {
return {
x: (reverse) ? (x * cos + y * sin) : (x * cos - y * sin),
y: (reverse) ? (y * cos - x * sin) : (y * cos + x * sin)
};
}

function checkCollision (ball0, ball1) {
var dx = ball1.x – ball0.x,
dy = ball1.y – ball0.y,
dist = Math.sqrt(dx * dx + dy * dy);

//collision handling code here
//calculate angle, sine, and cosine
var angle = Math.atan2(dy, dx),
sin = Math.sin(angle),
cos = Math.cos(angle),

//rotate ball0's position
pos0 = {x: 0, y: 0}, //point

//rotate ball1's position
pos1 = rotate(dx, dy, sin, cos, true),

//rotate ball0's velocity
vel0 = rotate(ball0.vx, ball0.vy, sin, cos, true),

//rotate ball1's velocity
vel1 = rotate(ball1.vx, ball1.vy, sin, cos, true),

//collision reaction
vxTotal = vel0.x – vel1.x;
vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) /
(ball0.mass + ball1.mass);
vel1.x = vxTotal + vel0.x;

//update position
pos0.x += vel0.x;
pos1.x += vel1.x;

//rotate positions back
var pos0F = rotate(pos0.x, pos0.y, sin, cos, false),
pos1F = rotate(pos1.x, pos1.y, sin, cos, false);

//adjust positions to actual screen positions
ball1.x = ball0.x + pos1F.x;
ball1.y = ball0.y + pos1F.y;
ball0.x = ball0.x + pos0F.x;
ball0.y = ball0.y + pos0F.y;

//rotate velocities back
var vel0F = rotate(vel0.x, vel0.y, sin, cos, false),
vel1F = rotate(vel1.x, vel1.y, sin, cos, false);
ball0.vx = vel0F.x;
ball0.vy = vel0F.y;
ball1.vx = vel1F.x;
ball1.vy = vel1F.y;
}
}


I believe your answer is due to the fact your circles are still in contact with each other and that the last line of the first function you post continues to update their positions to positions that are within the boundaries of each other. This will cause the collision routine to continue to keep them stuck where they are. At slower speeds the first function updates their velocity and position away from each other allowing them to move away as their positions no longer overlap.

• I don't understand this, the last lines of the function just sets the new velocity vectors of the balls so that they can move in new directions. – Kushagra Jul 8 '12 at 13:37

A simple solution that I have implemented before is when you detect a collision between objects do some simple math to pull the apart from one another before doing the collision calculation. So if two spheres collide, separate them by the sum of their radii and by the angle they collided then do your calculation.

To resolve the colision i made a Vector class with some methods like: 'add()', 'sub()', 'scale()', 'get_angle()', 'get_length()', 'set_angle()', 'set_length()' and 'norm_reflect()'.

The last one might be a little tricky so i'll show you how i did it:

def norm_reflect(self, vec1):

if vec1.get_angle() >= 0:    # if angle of vector is positive then add 90 to it

return self.set_angle(vec1.get_angle() - self.get_angle() + 90)

else:                        # else, the angle is negative so you subtract 90 from it

return self.set_angle(vec1.get_angle() - self.get_angle() - 90)


To do the collision now its just simple:

1- See if they collide

2- If they do calculate how much you need to move them so they don't collide

dist.set_length(- dist.get_length() + total_radius).scale(0.5)


3- Then move one circle one way and the other the other way

4- Then make the velocities like this:

b1.speed = b2.speed.add(b1.speed.norm_reflect(dist.set_angle(dist.get_angle() + 90)))

• Hi alex, you can click on the edit link at the bottom of your answer to complement it as needed. – petervaz Mar 28 '13 at 13:55