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.
You have quantum-jumping balls:
Before digging any deeper, you should strongly consider switching to fixed-delta physics:
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.Edit: And there's a second problem. Bigger one :-)
Once two balls touch, after you flip the direction, they may still touch. Then, at the next frame, you will flip the direction again, making the ball continue. You need to check if ball A intersect ball B and ball A's velocity is toward ball B before flipping ball A's direction.
I don't have the time to run and debug your code, but, approximately, something like: