C++物理引擎中球体碰撞时互相穿透的问题排查
球体碰撞穿透与错误反弹的修复方案
你的代码存在三个核心问题导致球体穿透和异常反弹:
- 碰撞检测时机滞后:先更新位置再检测碰撞,此时球体已经互相穿透,速度修正无法立刻解决穿透状态。
- 碰撞响应不符合物理规律:直接反转速度是错误的,正确的弹性碰撞需要基于动量守恒和碰撞法线计算速度变化。
- 未处理穿透分离:检测到碰撞后没有将球体从穿透位置移开,导致持续触发错误的碰撞逻辑。
修复后的核心代码实现
1. 修正updateBall函数逻辑
调整执行顺序,增加穿透分离和正确的碰撞响应:
void updateBall(Ball &ball, float gravity, float frictionCoefficient, float deltaTime, float screenWidth, float screenHeight, float bounciness, std::list<Ball> &balls) { // 应用重力(考虑时间缩放) ball.velocity.y -= gravity * deltaTime; // 处理球体间碰撞 for (auto &otherBall : balls) { if (&otherBall == &ball) continue; float currentDistance = Vector2Distance(ball.position, otherBall.position); float minDistance = ball.radius + otherBall.radius; if (currentDistance < minDistance) { // 计算碰撞法线 Vector2 normal = Vector2Normalize(Vector2Subtract(otherBall.position, ball.position)); // 分离穿透的球体 float penetration = minDistance - currentDistance; Vector2 move = Vector2Scale(normal, penetration * 0.5f); ball.position = Vector2Subtract(ball.position, move); otherBall.position = Vector2Add(otherBall.position, move); // 弹性碰撞速度计算(假设质量与半径成正比) float massA = ball.radius; float massB = otherBall.radius; Vector2 relativeVelocity = Vector2Subtract(otherBall.velocity, ball.velocity); float velAlongNormal = Vector2DotProduct(relativeVelocity, normal); // 球体正在远离则不处理 if (velAlongNormal > 0) continue; // 计算冲量 float j = -(1 + bounciness) * velAlongNormal; j /= (1/massA + 1/massB); // 修正速度 Vector2 impulse = Vector2Scale(normal, j); ball.velocity = Vector2Subtract(ball.velocity, Vector2Scale(impulse, 1/massA)); otherBall.velocity = Vector2Add(otherBall.velocity, Vector2Scale(impulse, 1/massB)); } } // 应用地面/天花板摩擦 bool isOnGround = (ball.position.y >= screenHeight - ball.radius); bool isOnCeiling = (ball.position.y <= ball.radius); if (isOnGround || isOnCeiling) { ball.velocity.x *= frictionCoefficient; // 添加微小Y速度避免球体粘在地面 if (isOnGround) ball.velocity.y = fmax(ball.velocity.y, 1.0f); } // 更新位置 ball.position.x += ball.velocity.x * deltaTime; ball.position.y += -ball.velocity.y * deltaTime; // 屏幕边界碰撞处理 if (ball.position.x <= ball.radius) { ball.position.x = ball.radius; ball.velocity.x *= -bounciness; } else if (ball.position.x >= screenWidth - ball.radius) { ball.position.x = screenWidth - ball.radius; ball.velocity.x *= -bounciness; } if (ball.position.y <= ball.radius) { ball.position.y = ball.radius; ball.velocity.y *= -bounciness; } else if (ball.position.y >= screenHeight - ball.radius) { ball.position.y = screenHeight - ball.radius; ball.velocity.y *= -bounciness; } }
2. 完整修正后的代码
#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 * deltaTime; // 处理球体间碰撞 for (auto &otherBall : balls) { if (&otherBall == &ball) continue; float currentDistance = Vector2Distance(ball.position, otherBall.position); float minDistance = ball.radius + otherBall.radius; if (currentDistance < minDistance) { // 计算碰撞法线 Vector2 normal = Vector2Normalize(Vector2Subtract(otherBall.position, ball.position)); // 分离穿透的球体 float penetration = minDistance - currentDistance; Vector2 move = Vector2Scale(normal, penetration * 0.5f); ball.position = Vector2Subtract(ball.position, move); otherBall.position = Vector2Add(otherBall.position, move); // 弹性碰撞速度计算(假设质量与半径成正比) float massA = ball.radius; float massB = otherBall.radius; Vector2 relativeVelocity = Vector2Subtract(otherBall.velocity, ball.velocity); float velAlongNormal = Vector2DotProduct(relativeVelocity, normal); // 球体正在远离则不处理 if (velAlongNormal > 0) continue; // 计算冲量 float j = -(1 + bounciness) * velAlongNormal; j /= (1/massA + 1/massB); // 修正速度 Vector2 impulse = Vector2Scale(normal, j); ball.velocity = Vector2Subtract(ball.velocity, Vector2Scale(impulse, 1/massA)); otherBall.velocity = Vector2Add(otherBall.velocity, Vector2Scale(impulse, 1/massB)); } } // 应用地面/天花板摩擦 bool isOnGround = (ball.position.y >= screenHeight - ball.radius); bool isOnCeiling = (ball.position.y <= ball.radius); if (isOnGround || isOnCeiling) { ball.velocity.x *= frictionCoefficient; // 添加微小Y速度避免球体粘在地面 if (isOnGround) ball.velocity.y = fmax(ball.velocity.y, 1.0f); } // 更新位置 ball.position.x += ball.velocity.x * deltaTime; ball.position.y += -ball.velocity.y * deltaTime; // 屏幕边界碰撞处理 if (ball.position.x <= ball.radius) { ball.position.x = ball.radius; ball.velocity.x *= -bounciness; } else if (ball.position.x >= screenWidth - ball.radius) { ball.position.x = screenWidth - ball.radius; ball.velocity.x *= -bounciness; } if (ball.position.y <= ball.radius) { ball.position.y = ball.radius; ball.velocity.y *= -bounciness; } else if (ball.position.y >= screenHeight - ball.radius) { ball.position.y = screenHeight - ball.radius; ball.velocity.y *= -bounciness; } } 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 = 981.0f; // 适配像素坐标系的重力值 const float frictionCoefficient = 0.98f; const float bounciness = 0.7f; Ball ballOne = newBall({100, 100}, {400, -450}, 60, WHITE); Ball ballTwo = newBall({500, 200}, {400, 450}, 45, BLUE); Ball ballThree = newBall({200, 500}, {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; }
关键说明
- 碰撞处理移到了位置更新之前,先解决穿透问题再移动球体。
- 使用动量守恒计算碰撞后的速度,反弹效果更符合真实物理规律。
- 增加了穿透分离逻辑,确保球体不会卡在彼此内部。
- 重力值调整为
981.0f(像素/秒²),适配Raylib的像素坐标系,效果更直观。
内容的提问来源于stack exchange,提问作者metror
相关产品推荐
相关产品推荐

