The last part covered the collisions between balls, what about collisions with the sides of the table? If the balls are not spinning and there is no ball-table friction the component of the ball velocity that is parallel to the table side remains the same, and the component perpendicular to the side changes direction and intensity based on the coefficient of restitution. Using primed letters for the velocity after the collisions, the formula for a collision with the left or right side is:
$$ v’ = –cv $$
$$ w’ = w $$
where \(c\) is the ball-table coefficient of restitution (that in general is different from the ball-ball one). The formula for a collision with the ‘top’ (as seen on the screen) or ‘bottom’ side is:
$$ v’ = v $$
$$ w’ = -cw $$
With this last piece, the complete update code is:
update(dt: number) {
while (dt > 0) {
var firstCollisions = this.detectCollisions(dt, 0, 400, 0, 300);
if (firstCollisions.t > 0) {
// The balls move freely up to the time of the first collision: update their positions
for (var i = 0; i < this.balls.length; i++) {
var ball = this.balls[i];
ball.updatePosition(firstCollisions.t);
}
}
// Compute the new velocities after the collisions
for (var i = 0; i < firstCollisions.collisions.length; i++) {
var collision = firstCollisions.collisions[i];
switch (collision.type) {
case "x":
collision.b1.v = -collision.b1.v * this.parameters.sideRestitution;
break;
case "y":
collision.b1.w = -collision.b1.w * this.parameters.sideRestitution;
break;
case "b":
collision.b1.collide(collision.b2, this.parameters.ballRestitution);
break;
}
}
// Continue with the remaining time
dt -= firstCollisions.t;
}
this.draw();
} // update
where ‘detectCollisions’ is a function that uses the code described previously to detect the first collision – or collisions – that occur within the specified interval. It returns an object containing the time of the collision and an array of the balls that collide. Each element of this array has two members ‘b1’ and ‘b2’ containing the colliding balls (‘b2’ is null for collisions with the sides) plus a ‘type’ member that indicates if the collision is with the left or right sides (“x”), with the top or bottom sides (“y”) or between two balls (“b”).
Here is the ‘detectCollisions’ code:
private detectCollisions(dt: number, minx: number, maxx: number, miny: number, maxy: number) {
var result = {
t: dt,
collisions: <{ type: string; b1: Ball; b2: Ball; }[]>[]
}
var addCollision = (t, collision) => {
if (t === result.t) {
// The new collision happens at the exact same time of the current one, it has to be added to the list
result.collisions.push(collision);
} else {
// The new collision happens before the current one, so it replaces the entire list
result.collisions = [collision];
result.t = t;
}
}
for (var i = 0; i < this.balls.length; i++) {
// Collisions with the sides
var ball = this.balls[i];
var t = ball.sideXCollisionTime(minx, maxx);
if (t && t <= result.t) {
addCollision(t, { type: "x", b1: ball, b2: <Ball>null });
}
t = ball.sideYCollisionTime(miny, maxy);
if (t && t <= result.t) {
addCollision(t, { type: "y", b1: ball, b2: <Ball>null });
}
// Ball-ball collisions
for (var j = i + 1; j < this.balls.length; j++) {
var otherBall = this.balls[j];
t = ball.collisionTime(otherBall);
if (t && t <= result.t) {
addCollision(t, { type: "b", b1: ball, b2: otherBall });
}
}
}
return result;
} // detectCollisions
As promised in the title this code has a problem though: it works fine in most cases but fails when there are multiple ball-ball collisions at the same time.
Have a look at this: the ball coming from the left hits the two balls at rest at the same time, but the code evaluates the collisions one at a time: it first consider the collisions with the ball at the top, updating the velocity of the moving ball to move down, and then uses this velocity to compute the second collision. The result is clearly wrong: the moving ball should bounce straight back, and the result should not depend on the order of the balls.
I have no solution for this at the moment being.