JS物理模拟中移动球体被固定球体卡住无法弹开的问题排查
移动球体被固定球体卡住的碰撞检测问题分析与修复
问题场景
基于Matthias Müller《10分钟物理》第3课修改的台球碰撞模拟,采用SVG实现,支持添加球体、设置固定障碍物及暂停功能,但存在移动球被固定球卡住无法逃脱的问题,例如启动后红球会立即被黑球卡住。
问题代码
碰撞处理核心逻辑:
function handleBallCollision(ball1, ball2, restitution) { const pushVel = 2.0; const dir = new Vector2(); dir.subtractVectors(ball2.pos, ball1.pos); const d = dir.length(); const rsum = ball1.radius + ball2.radius; if (d == 0.0 || d > rsum) return; dir.scale(1.0 / d); let corr = (ball1.radius + ball2.radius - d) / 2.0; if (ball1.isMoving == 0 || ball2.isMoving == 0) { const ball = ball1.isMoving == 0 ? ball2 : ball1; ball.pos.add(dir, corr); var v = ball.vel.dot(dir); ball.vel.add(dir, pushVel - v); } // else handle case of two balls moving... }
问题原因
位移修正量错误:
代码中corr = (rsum - d)/2是两个移动球碰撞时的修正逻辑(两球各移动一半距离分离),但当其中一个球固定时,固定球不会移动,移动球需要移动全部的分离距离(rsum - d)才能脱离重叠状态。仅移动一半距离会导致两球始终重叠,下一帧再次触发碰撞检测,形成循环卡顿。速度调整逻辑不符合物理规律:
强制将移动球的法线方向速度设置为固定值pushVel,而非基于弹性碰撞的反弹逻辑,可能导致速度方向或大小异常,加剧卡顿现象。
修复方案
修改固定球碰撞分支的逻辑,修正位移量并替换为正确的弹性碰撞速度处理:
function handleBallCollision(ball1, ball2, restitution) { const dir = new Vector2(); dir.subtractVectors(ball2.pos, ball1.pos); const d = dir.length(); const rsum = ball1.radius + ball2.radius; if (d == 0.0 || d > rsum) return; dir.scale(1.0 / d); if (ball1.isMoving === 0 || ball2.isMoving === 0) { // 区分固定球和移动球 const fixedBall = ball1.isMoving === 0 ? ball1 : ball2; const movingBall = ball1.isMoving === 0 ? ball2 : ball1; // 修正位移:移动球移动全部分离距离远离固定球 const corr = rsum - d; movingBall.pos.add(dir, corr); // 修正速度:法线方向弹性反弹 const v = movingBall.vel.dot(dir); // 仅当移动球朝向固定球时触发反弹 if (v < 0) { // 移除原法线方向速度分量,添加反弹后的分量 movingBall.vel.add(dir, -2 * v * restitution); } } else { // 处理两移动球碰撞的原有逻辑... } }
关键改动说明
- 位移修正:将
corr从(rsum - d)/2改为rsum - d,确保移动球一次性完全脱离与固定球的重叠区域。 - 速度修正:使用弹性碰撞公式,将移动球在法线方向的速度分量反向并乘以恢复系数,仅当移动球朝向固定球时触发,符合真实物理反弹效果。
- 逻辑清晰化:明确区分固定球与移动球,避免混淆碰撞双方的角色。
内容的提问来源于stack exchange,提问作者Ken
相关产品推荐
相关产品推荐

