I have created a 2D physics simulation in rust that works for the most part. It involves creating balls which collide with each other and their container. I have looked at a lot of resources and found that this and this have been the most helpful (there are suprisingly few resources for collisions of two moving balls in 2D).
The maths I used to model the collision is specified in this pdf: https://www.vobarian.com/collisions/2dcollisions2.pdf.
Below is a video of what problem occurs:

As you can see, some of the balls will get stuck inside of each other, rotate a little and then pop out of each other. It is very unsual behaviour and shouldn't be possible given that I have (as far as I am aware) implemented the maths correctly. Below is my collision function, which is iterated through on every ball.
fn collide(ball_1: &mut Ball, ball_2: &mut Ball) {
// Checking for collisions in next frame to stop balls from overlapping, so that they won't get "trapped"
let next_pos_1 = ball_1.pos + ball_1.vel;
let next_pos_2 = ball_2.pos + ball_2.vel;
let distance =((next_pos_1[0]-next_pos_2[0]).powf(2.0)+(next_pos_1[1]-next_pos_2[1]).powf(2.0)).sqrt();
if distance < ball_1.radius + ball_2.radius {
let normal_vector = ball_2.pos - ball_1.pos;
let unit_normal = normal_vector / (normal_vector[0].powi(2) + normal_vector[1].powi(2)).powf(0.5);
let unit_tangent = Vec2::new(-unit_normal[1], unit_normal[0]);
let v_1_in_norm_dir = unit_normal.dot(ball_1.vel);
let v_2_in_norm_dir = unit_normal.dot(ball_2.vel);
let v_1_in_tan_dir = unit_tangent.dot(ball_1.vel);
let v_2_in_tan_dir = unit_tangent.dot(ball_2.vel);
let new_v_1_in_norm_dir = v_2_in_norm_dir;
let new_v_2_in_norm_dir = v_1_in_norm_dir;
ball_1.vel = new_v_1_in_norm_dir * unit_normal + v_1_in_tan_dir * unit_tangent;
ball_2.vel = new_v_2_in_norm_dir * unit_normal + v_2_in_tan_dir * unit_tangent;
let next_pos_1 = ball_1.pos + ball_1.vel;
let next_pos_2 = ball_2.pos + ball_2.vel;
let distance =((next_pos_1[0]-next_pos_2[0]).powf(2.0)+(next_pos_1[1]-next_pos_2[1]).powf(2.0)).sqrt();
// Edge case where the ball's collision will cause one to push the other, so just push them back apart
if distance >= ball_1.radius +ball_2.radius {
let overlap = ((ball_1.radius + ball_2.radius) - distance)/2.0;
let m = (ball_1.pos[1]-ball_2.pos[1])/(ball_1.pos[0]-ball_2.pos[0]);
let theta = m.atan().abs();
let y_change = overlap*theta.sin();
let x_change = overlap*theta.cos();
if ball_1.pos[0] <= ball_2.pos[0] {
ball_1.pos[0] -= x_change;
ball_2.pos[0] += x_change;
ball_1.pos[1] -= y_change;
ball_2.pos[1] += y_change;
} else {
ball_1.pos[0] += x_change;
ball_2.pos[0] -= x_change;
ball_1.pos[1] += y_change;
ball_2.pos[1] -= y_change;
}
}
}
}
The last if statement is trying to combat my current problem. My current theory on what is occuring is that sometimes, when the velocities of the balls are calculated correctly, these velocities will actually force the balls into one another (because the velocity of one ball points towards the other ball). When the velocities are then added onto the positions at the end of the function, it causes one ball to overlap with the other. This is problematic as this function is supposed to only trigger when the balls are about to overlap or are already overlapping. This is therefore causing the balls to overlap more and more and just create a kind of loop, which eventually ends. It is causing the collision function to do the opposite of what it is supposed to do.
I might be wrong about this theory but it is quite difficult to debug. I am not a physicist (clearly) and so I am struggling with figuring out what the best thing to do here is. I want to keep the accurate collisions and processing speed but lose this glitchy behaviour.
I couldn't find any posts talking about this but please feel free to give me any advice as I am feeling a bit lost! Thanks :)
hypotshould be faster and have less rounding errors than(x.powi (2) + y.powi (2)).sqrt()(which should be faster and more precise than usingpowf (2.0)andpowf (0.5))1; the computation is done for just onetime step. It seems to me that in the collsion case, you compute two time steps for those balls, one with the initial velocities and one with the final ones, then you bring the balls back in contact; but why shoukd the collided balls be in contact after a time step?if distance < ...), you would 1) find the fraction of the time step when the balls where actually tangent; 2) interpolate (back) the positions to that fraction of time step; 3) compute the new velocities (as you did); and 4) move the balls away from the collision point (using the new velocities) for the remaining fraction of the time step. And after that, multiple ball collisions should be addressed, and especially ball-ball-wall collisions, but this, of course, is not about that.