如果已经知道了两个相互碰撞的物体进行碰撞前的 线速度, 角速度, 质心坐标, 被碰撞点的坐标, 碰撞方向, 质量, 转动惯量, 恢复系数, 那么可以用以下公式求得两个物体对对方造成的碰撞冲量大小:
J = -vr (e + 1) / ( 1/m1 + 1/m2 + n * ((r1 × n) / I1) × r1 + n * ((r2 × n) / I2) × r2 )
其中:
m1与m2是两物体的质量
I1与I2是两物体的转动惯量
e是恢复系数
r1与r2是从物体质心指向被碰撞点的向量, 也就是力臂
vr = v1‘ - v2‘
v1‘ = v1 + r1×ω1, 即把角速度转成线速度也加到速度上来进行计算
n是两物体的碰撞方向向量, 上一篇有提到如何求
求得J之后, 将冲量分别施加在两物体上可以得到物体被碰撞后的线速度和角速度:
v1+ = v1- + (Jn) / m1
ω1+ = ω1- + (r1 × Jn) / I1
v2+ = v2- + (- Jn) / m2
ω2+ = ω2- + (r2 × - Jn) / I2
上面的公式可以在《游戏开发物理学》的103页找到, 有写如何推导. 也可以看这里的最后一部分.
applyImpulse: function (oImpulse, oPosition) { this.velocity = this.velocity.add(oImpulse.mul(this.inverseMass)); this.angularVelocity += oPosition.sub(this.centroid).cross(oImpulse) * this.inverseInertia; }
applyCollisionImpulse: function (bodyA, bodyB, contactPoint, normal, e) { var cp = contactPoint, rA = cp.sub(bodyA.centroid), rB = cp.sub(bodyB.centroid), vA = bodyA.velocity.add(rA.scalarCross(bodyA.angularVelocity)), vB = bodyB.velocity.add(rB.scalarCross(bodyB.angularVelocity)), invIA = bodyA.inverseInertia, invIB = bodyB.inverseInertia, invMA = bodyA.inverseMass, invMB = bodyB.inverseMass, vr = vA.sub(vB), rsnA = rA.cross(normal), rsnB = rB.cross(normal), kn = invMA + invMB + rsnA * rsnA * invIA + rsnB * rsnB * invIB, jn = -(1 + e) * vr.dot(normal) / kn, impulse = normal.mul(jn); bodyA.applyImpulse(impulse, cp); bodyB.applyImpulse(impulse.negate(), cp); }
2D向量与标量叉乘可以这样算:
scalarCross: function (num) { return new Vector2D(-num * this.y, num * this.x); }
如果希望添加摩擦力, 可以这样修改:
applyCollisionImpulse: function (bodyA, bodyB, cp, normal, e, friction) { var rA = cp.sub(bodyA.centroid), rB = cp.sub(bodyB.centroid), vA = bodyA.velocity.add(rA.scalarCross(bodyA.angularVelocity)), vB = bodyB.velocity.add(rB.scalarCross(bodyB.angularVelocity)), invIA = bodyA.inverseInertia, invIB = bodyB.inverseInertia, invMA = bodyA.inverseMass, invMB = bodyB.inverseMass, vr = vA.sub(vB), rsnA = rA.cross(normal), rsnB = rB.cross(normal), kn = invMA + invMB + rsnA * rsnA * invIA + rsnB * rsnB * invIB, jn = -(1 + e) * vr.dot(normal) / kn, impulse = normal.mul(jn); bodyA.applyImpulse(impulse, cp); bodyB.applyImpulse(impulse.negate(), cp); vA = bodyA.velocity.add(rA.scalarCross(bodyA.angularVelocity)); vB = bodyB.velocity.add(rB.scalarCross(bodyB.angularVelocity)); vr = vA.sub(vB); var tangent = vr.sub(normal.mul(vr.dot(normal))).normalize(), rstA = rA.cross(tangent), rstB = rB.cross(tangent), kt = invMA + invMB + rstA * rstA * invIA + rstB * rstB * invIB, jf = Math.min(vr.dot(tangent) / kt, Math.abs(jn * friction)), tangentImpulse = tangent.mul(jf); bodyA.applyImpulse(tangentImpulse.negate(), cp); bodyB.applyImpulse(tangentImpulse, cp); }
其中jf = Math.min(vr.dot(tangent) / kt, Math.abs(jn * friction))中的jn * friction就是 μ * 压力冲量, vr.dot(tangent) / kt 是使得摩擦力恰好使切线方向速度变成0的冲量大小, 因为摩擦力会在相对速度为0时变成0, 不如此计算最大摩擦冲量的话施加摩擦冲量后可能会导致切向方向物体反向加速.
仅靠碰撞冲量实际无法完全分离两个物体, 因为物体可以相互嵌入而不是恰好接触. 已知两物体的相互嵌入距离的话可以用下面的方法修正物体的位置:
correctPosition: function (bodyA, bodyB, penetration) { var maxPenetration = 0.05, correctPercent = 0.4, correction, tA, tB; if (penetration <= maxPenetration) { return; } correction = this.normal.mul((penetration - maxPenetration) / (bodyA.inverseMass + bodyB.inverseMass) * correctPercent); tA = correction.negate().mul(bodyA.inverseMass); tB = correction.mul(bodyB.inverseMass); bodyA.translate(tA.x, tA.y); bodyB.translate(tB.x, tB.y); return; }
这里有简单解释这个函数是如何工作的.