MP13
MP13

Reputation: 410

C++ / SDL2 - Ball bouncing/glitching together

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.

Two Balls stucking together

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

Answers (1)

G. Sliepen
G. Sliepen

Reputation: 7973

The proper fix

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.

The workaround

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.

Other issues in your code

Note that there are also some other thing in your code that you could improve:

  • Don't 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.
  • You don't need to call abs() if you are going to square the result anyway.
  • Use std::hypot() to calculate the distance.
  • Did you write 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.
  • Use a proper value of π. A simple, portable solution is to declare static constexpr pi = std::atan(1) * 4.

Upvotes: 1

Related Questions