Balls phasing through each other in physics engine

I’m trying to write a simple physics engine in C++; it’s been going well so far, and all of the functions work as intended except for the physics between the balls themselves. During collisions, the balls will sometimes just phase through eachother.

#include <iostream>
#include <stdio.h>
#include <raylib.h>
#include <raymath.h>
#include <stdlib.h>
#include <time.h>
#include <chrono>
#include <list>

using namespace std;

class Ball {
public:
    Vector2 position;
    Vector2 velocity;
    float radius;
    Color color;
};

void updateBall(Ball &ball, float gravity, float frictionCoefficient, float deltaTime, float screenWidth, float screenHeight, float bounciness, std::list<Ball> &balls) {

    ball.velocity.y -= gravity;

    // Apply friction if the ball is at the top or bottom of the screen
    ball.velocity.x *= (ball.position.y >= screenHeight - ball.radius || ball.position.y <= ball.radius) ? frictionCoefficient : 1;

    // Update the ball's position based on its velocity
    ball.position.x += ball.velocity.x * deltaTime;
    ball.position.y += -ball.velocity.y * deltaTime;

    // Clamp the ball's position within the screen bounds
    ball.position.x = Clamp(ball.position.x, ball.radius, screenWidth - ball.radius);
    ball.position.y = Clamp(ball.position.y, ball.radius, screenHeight - ball.radius);

    // Check for collisions with screen edges and apply bounciness
    ball.velocity.x *= (ball.position.x == ball.radius || ball.position.x == screenWidth - ball.radius) ? -bounciness : 1;
    ball.velocity.y *= (ball.position.y == ball.radius || ball.position.y == screenHeight - ball.radius) ? -bounciness : 1;

    for (auto &otherBall : balls) {
        // Avoid checking the ball against itself
        if (&otherBall == &ball) {
            continue;
        }

        // Calculate the distance between the two balls
        float distance = Vector2Distance(ball.position, otherBall.position);

        // Check if a collision has occurred
        if (distance < (ball.radius + otherBall.radius)) {
            ball.velocity.x = -(ball.velocity.x);
            otherBall.velocity.x = -(otherBall.velocity.x);
            ball.velocity.y = -(ball.velocity.y);
            otherBall.velocity.y = -(otherBall.velocity.y);
        }
    }
}

Ball newBall(Vector2 position, Vector2 velocity, float radius, Color color) {
    Ball tempBall;
    tempBall.position = position;
    tempBall.velocity = velocity;
    tempBall.radius = radius;
    tempBall.color = color;
    return tempBall;
}

int main() {
    SetConfigFlags(FLAG_WINDOW_RESIZABLE);

    int screenWidth = 900;
    int screenHeight = 900;

    InitWindow(screenWidth, screenHeight, "cppPhysics");
    SetTargetFPS(120);

    const float gravity = 9.81f;
    const float frictionCoefficient = 0.981f;
    const float bounciness = 0.7f;

    Ball ballOne = newBall({100, 100}, {400, -450}, 60, WHITE);
    Ball ballTwo = newBall({50, 200}, {400, 450}, 45, BLUE);
    Ball ballThree = newBall({200, 50}, {300, -450}, 20, YELLOW);

    std::list<Ball> balls;
    balls.push_back(ballOne);
    balls.push_back(ballTwo);
    balls.push_back(ballThree);

    while (!WindowShouldClose()) {
        if (IsWindowResized()) {
            screenWidth = GetScreenWidth();
            screenHeight = GetScreenHeight();
        }
        float deltaTime = GetFrameTime();

        for (auto &ball : balls) {
            updateBall(ball, gravity, frictionCoefficient, deltaTime, screenWidth, screenHeight, bounciness, balls);
        }

        if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) {
            for (auto &ball : balls) {
                ball.velocity.y += 500;
                ball.velocity.x += 500;
            }
        }

        BeginDrawing();
        ClearBackground(BLACK);

        for (auto &ball : balls) {
            DrawCircleV(ball.position, ball.radius, ball.color);
        }

        EndDrawing();
    }

    CloseWindow();
    return 0;
}

Note: The code here is shortened; the actual program i’m making uses randomized values for the mouseclick and color values.

Optimally, they would bounce away from eachother (kind of like how they do off the ground). I’ve already tried moving the code to the start of the function, and making the function break if a collision is detected.

  • A frequent issue is “multiple” collisions with the same two objects negating twice the velocity.

    – 

  • It may be that; how would I go about fixing that, though?

    – 

  • Negate the velocity when collision AND when they go in direction of the collided object.

    – 

  • How would I find when the balls are going in the same direction? Could you provide a code example?

    – 

  • sign of dot product between velocity, and positions difference.

    – 

You have quantum-jumping balls:

float deltaTime = GetFrameTime();
updateBall(ball, gravity, frictionCoefficient, deltaTime, screenWidth, screenHeight, bounciness, balls);

ball.position.x += ball.velocity.x * deltaTime;
ball.position.y += -ball.velocity.y * deltaTime;

Before digging any deeper, you should strongly consider switching to fixed-delta physics:

accumulatedDelta += GetFrameTime(); // accumulatedDelta is a member value
while(accumulatedDelta > someConstant)
{
    updateBall(ball, gravity, frictionCoefficient, someConstant, screenWidth, screenHeight, bounciness, balls);  
    accumulatedDelta -= someConstant;
}

That will rid you of most phasing. It will make your physics less frame-duration-dependent and if you ever get to multiplayer, your synchronisation and determinism will be some much easier to deal with.

You’ll need to pick someConstant with care. Too small, you’ll do too much physics. Too large and you get phasing and/or balls that don’t update every frame. I like 1 hundredth of a second, unless my frame rate is very high.

Leave a Comment