Reputation: 410
I was trying to write some ball bouncing program in C++ using SDL2. I had a hard time getting the velocity exchange correct, but it works pretty neat so far. The only problem I have right now is that the balls are sometimes glitching/stucking together and after some seconds they release themself again.
That is my update()
function which gets called every frame:
void Game::update() {
updateFPS();
checkBallCollision();
updateCanCollide();
int newtime = SDL_GetTicks();
int diff = newtime - lasttime;
if (diff > 10)
diff = 10;
for (Ball *ball : balls) {
ball->x = ball->x + ball->velocity->x * (float) diff / 100;
ball->y = ball->y + ball->velocity->y * (float) diff / 100;
checkBorderCollision(ball);
}
lasttime = newtime;
}
I guess that the balls are getting to close and don't bounce at the border of the balls. Therefore I tried to give every ball a boolean canCollide
which is always true except a ball is colliding. Then it stays false until the two balls aren't overlapping anymore.
Here are my checkBallCollision()
and updateCanCollide()
functions:`
void Game::updateCanCollide() {
Ball **ballArr = &balls[0];
int length = balls.size();
for (int i = 0; i < length; i++) {
if (ballArr[i]->canCollide)
continue;
bool updatedCollide = true;
for (int k = i + 1; k < length; k++) {
Ball *ball1 = ballArr[i];
Ball *ball2 = ballArr[k];
int xdiff = abs(ball1->x - ball2->x);
int ydiff = abs(ball1->y - ball2->y);
float distance = sqrt(xdiff * xdiff + ydiff * ydiff);
if (distance <= ball1->radius + ball2->radius) {
updatedCollide = false;
}
}
ballArr[i]->canCollide = updatedCollide;
}
}
// do all collision checks and update the velocity
void Game::checkBallCollision() {
Ball **ballArr = &balls[0];
int length = balls.size();
for (int i = 0; i < length; i++) {
if (!ballArr[i]->canCollide)
continue;
for (int k = i + 1; k < length; k++) {
if (!ballArr[k]->canCollide)
continue;
Ball *ball1 = ballArr[i];
Ball *ball2 = ballArr[k];
int xdiff = abs(ball1->x - ball2->x);
int ydiff = abs(ball1->y - ball2->y);
float distance = sqrt(xdiff * xdiff + ydiff * ydiff);
if (distance <= ball1->radius + ball2->radius) {
// ball1 and ball2 are colliding
// update the velocity of both balls
float m1 = ball1->radius * ball1->radius * 3.14159;
float m2 = ball2->radius * ball2->radius * 3.14159;
Vector2D *v1 = new Vector2D(ball1->velocity->x, ball1->velocity->x);
Vector2D *v2 = new Vector2D(ball2->velocity->x, ball2->velocity->x);
ball1->velocity->x = ((v1->x * (m1 - m2) + 2 * m2 * v2->x) / (m1 + m2));
ball1->velocity->y = ((v1->y * (m1 - m2) + 2 * m2 * v2->y) / (m1 + m2));
ball2->velocity->x = ((v2->x * (m2 - m1) + 2 * m1 * v1->x) / (m1 + m2));
ball2->velocity->y = ((v2->y * (m2 - m1) + 2 * m1 * v1->y) / (m1 + m2));
ball1->canCollide = false;
ball2->canCollide = false;
}
}
}
}
Upvotes: 2
Views: 869
Reputation: 7973
The main problem is that you are letting the balls overlap each other, then update their velocities. However, if the next time step is shorter than the previous one, it can be that after updating their positions, they are still overlapping. Then you think they are colliding again, and update their velocities, but this will most likely cause then to move closer together again. This explains why they get stuck.
The proper wait to solve this is to calculate the exact point in time that two moving balls collide. This can be done analytically, for example by treating time as a third dimension, and then calculating a line-sphere intersection. If this happens during the time step, you advance the time up to the point that the collision happens, then update the velocities, and then perform the rest of the step. If you have more than two balls, then be aware that you can have more than two balls colliding all with each other in the same timestep. This is also solvable, just calculate all the time points that collisions happen, select the earliest one, update velocities at that point, and then recalculate the collision times, and so on until there are no collisions in the time step.
Your workaround might fix two balls sticking to each other, but the result is not physically accurate. It breaks down when you start increasing the density of balls, since at some point the chance will be very high that at least one ball of a pair that should collide was in a collision in the previous timestep, and then they will all just start passing through each other all the time.
Another issue is that you have to check every possible pair of balls in updateCanCollide()
, which is not efficient. There is a simpler and more common workaround to this problem: when two balls collide, after updating their velocities, immediately update their positions as well such that the balls are no longer colliding. You can try to calculate exactly how much to move them so they no longer overlap, or if you don't want to involve mathematics, you can just have a while loop to do a small step until they no longer overlap.
Note that there are also some other thing in your code that you could improve:
new
a temporary Vector2D
, just declare it on the stack. If for some reason this is not possible, at least delete
v1
and v2
afterwards.abs()
if you are going to square the result anyway.std::hypot()
to calculate the distance.Vector2D
yourself or is it from a library? If the latter, maybe it already has functions to reflect two 2D vectors? If the former, consider using a library like GLM, even if you are not using OpenGL.static constexpr pi = std::atan(1) * 4
.Upvotes: 1