物理引擎中可能是浮点数误差造成的一个问题?
自己试着做个2d物理引擎玩时遇到的一个问题... 两个物体碰撞时计算摩擦冲量似乎很容易受浮点数精度的影响
两个物体碰撞时给予对方的碰撞冲量我按下面这个公式算了出来
其中速度差向量 vr = vA' - vB' , n是两物体分离轴的法线单位向量
另外把角速度也换算成线速度加到速度向量上来计算, 于是vA' = vA + rA×ωA
到这里都没什么问题
现在要求摩擦冲量, 令单位切线向量
$$tangent = normalize(vr - (vr*normal) * normal)$$
于是变成下图这样
按图1的上面的公式求得碰撞冲量大小j之后, 摩擦冲量大小$$jf = μ * j$$, 摩擦力在两个物体相对静止时就会变成0所以还要求一个最大摩擦冲量大小
vr在tangent上的投影的大小 $$vt = vr * tangent$$, 对两个物体施加摩擦冲量之后最多使得这个vt变成0但不能让vt的符号改变
我按照图1下面的4个公式以及△v = vt求得的最大jf是:
$$jfMax = vr * tangent / (1/mA + 1/mB + (rA×tangent)^2/IA + (rB×tangent)^2/IB)$$
于是摩擦冲量 $$tImpulse = tangent * min(jfMax, μ * j)$$
分别对物体A施加 -tImpulse, 对物体B施加 tImpulse, 如此实现摩擦
但是我发现在去除μ*j只适用jfMax的情况下, 最后vt并不等于0而是在0附近上下波动, 多数是0.01~0.5
bodyA.applyImpulse(tangentImpulse.negate(), cp);
bodyB.applyImpulse(tangentImpulse, cp);
vA = bodyA.velocity.add(rA.scalarCross(bodyA.angularVelocity));
vB = bodyB.velocity.add(rB.scalarCross(bodyB.angularVelocity));
console.log(vA.sub(vB).dot(tangent));
适用摩擦冲量前后, 有时动能反而会增大
k1 = bodyA.getKinetic() + bodyB.getKinetic();
bodyA.applyImpulse(tangentImpulse.negate(), cp);
bodyB.applyImpulse(tangentImpulse, cp);
k2 = bodyA.getKinetic() + bodyB.getKinetic();
if ((k2 - k1) > 0.1) console.log(((k2 - k1) / k1 * 100 | 0) + '%');
单个小物体会因为这个问题在与其它物体碰撞时明显的"抽搐"
求摩擦冲量的式子我翻了下别人的物理引擎基本都一样, 所以我觉得可能是浮点数的精度误差累积造成的问题
别人的代码里有看到一些误差修正的式子, 但搞不懂是怎么得出来的也不知道实际修正的是个啥...
另外我的一大坨代码如下
applyImpulse: function () {
var bodyA = this.bodyA,
bodyB = this.bodyB,
cp = this.contactPoint,
restitution = bodyA.restitution > bodyB.restitution ? bodyA.restitution : bodyB.restitution,
normal = this.normal,
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,
j = -(1 + restitution) * vr.dot(normal) / kn,
impulse = normal.mul(j);
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,
jfMax = vr.dot(tangent) / kt,
sf = Math.sqrt(bodyA.staticFriction * bodyB.staticFriction),
df = Math.sqrt(bodyA.dynamicFriction * bodyB.dynamicFriction),
jf = jfMax < Math.abs(j * sf) ? jfMax : Math.abs(j * df),
tangentImpulse = tangent.mul(jf);
bodyA.applyImpulse(impulse.add(tangentImpulse.negate()), cp);
bodyB.applyImpulse(impulse.negate().add(tangentImpulse), cp);
return this;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
像 box2d 这类的物理引擎有明确指出在 32 位浮点范围内它只能较精準处理 10cm 到 10m 范围内的物体,超出这个范围就会出现各种诡异现象。你处理的物体的尺寸在什么范围呢?
抱歉隔了好久才想起来... 最后发现其实只要先求出碰撞后的碰撞方向的速度, 再以此求摩擦力就对了, 浮点误差其实小得没有影响简单的乘个0.9995之类的系数就能避免摩擦力过大的, 似乎是和物体可以相互嵌入对方而实际上的刚体并不能如此有关
没看你的具体计算过程,但是如果对数值精度很敏感的话,应该是计算步骤太多了吧,试试精简一下计算过程吧。如果你只是追求精度,那么多用解析,少用数值,能在纸上推导的就不放到程序里去运算,这样做可能会破坏原有物理模型的封装。
高精度运算难道不要自己写库?
其實,完全沒必要如此精確,
過於精確就會導致不準確。
精準意味着在精確和準確當中平衡。
所以,這種情況,適當捨去一些精度就好了。
況且,有時你的那些公式本身的精準度可能都沒這麼高。
你的顯示器分辨率也沒這麼高。
再者,很小的不規則的震動,已經不是宏觀上整體的震動了。
以前我處理這種情況,解決方案是提升顯示的精度(相當於抗鋸齒),避免取整,用模糊代替「抽搐」,看上去會舒服很多。
另外還可以考慮一下動態模糊。對於消除精度過高帶來的誤差很有幫助。