就是一个物体掉落到另一个物体表面时,相互穿透,部分区域发生了重叠:
原因
现实世界,时间是连续的;但在计算机中,时间是离散的,帧与帧之间的时间间隔,没法保证两物体发生碰撞时,正好是表面刚接触。
比如:物体1和物体2相距6米,大小均为1x1,物体1向右以10米/s速度移动,物体2静止,0.5s后物体1和物体2的边缘正好刚发生接触。
同样的时间间隔,如果物体1是以12米/s的速度向右移动的,那0.5s后物体1和物体2就会有1米的穿透距离(重叠区域)。
解决办法
碰撞时,检查是否发生穿透,发生穿透我们就加一个修正用的速度让它们分离开。
这个速度用多大呢?v = 穿透距离 / 帧间隔时间
同时,为了防止用力过大,我们会加一个百分比参数来控制修正的程度:v = 修正程度 * 穿透距离 / 帧间隔时间
物理引擎中常用的方法叫:Baumgarte Stabilization
变化代码
//碰撞点信息 public class ContactInfo { public Vector2 m_Point; //碰撞点 public Vector2 m_Normal; //碰撞法向量(分离方向), 这边用A指向B, 即: B反弹方向 public float m_Penetration; //穿透深度(分离距离) public uint m_Fp; //用于识别两box的碰撞 public float m_ImpulseNormal; //法线方向累加冲量 public float m_ImpulseTangent; //切线方向累加冲量 public float m_MassNormal; //碰撞后速度计算公式中会用到: 1/m1+1/m2, 这里存放的就是这个结果 public float m_MassTangent; public float m_Bias; //穿透修正速度 }
MyPhysics的PreSeperation和PostSeperation变化了少量代码
private void PreSeperation(float dt, CollisionPair collisionPair) { const float allowedPenetration = 0.01f; const float biasFactor = 0.2f; float invDt = 1 / dt; var rigidbodyA = collisionPair.m_RigidbodyA; var rigidbodyB = collisionPair.m_RigidbodyB; for (int i = 0; i < collisionPair.m_NumContacts; ++i) { var contact = collisionPair.m_Contacts[i]; Vector2 ra = contact.m_Point - rigidbodyA.Position; Vector2 rb = contact.m_Point - rigidbodyB.Position; Vector2 normal = contact.m_Normal; Vector2 tangent = new Vector2(normal.y, -normal.x); //切线(顺时针) float kMassNormal = rigidbodyA.InvMass + rigidbodyB.InvMass; float raN = Vector2.Dot(ra, normal); float rbN = Vector2.Dot(rb, normal); kMassNormal += rigidbodyA.InvInertia * (Vector2.Dot(ra, ra) - raN * raN) + rigidbodyB.InvInertia * (Vector2.Dot(rb, rb) - rbN * rbN); contact.m_MassNormal = 1 / kMassNormal; float kMassTangent = rigidbodyA.InvMass + rigidbodyB.InvMass; float raT = Vector2.Dot(ra, tangent); float rbT = Vector2.Dot(rb, tangent); kMassTangent += rigidbodyA.InvInertia * (Vector2.Dot(ra, ra) - raT * raT) + rigidbodyB.InvInertia * (Vector2.Dot(rb, rb) - rbT * rbT); contact.m_MassTangent = 1 / kMassTangent; contact.m_Bias = -biasFactor * invDt * Mathf.Min(0, contact.m_Penetration + allowedPenetration); if (m_AccumulateImpulses) { Vector2 impulse = contact.m_ImpulseNormal * normal; //冲量大小转成冲量向量 impulse += contact.m_ImpulseTangent * tangent; //切线方向 rigidbodyA.ApplyImpulse(-impulse); rigidbodyA.ApplyTorqueImpulse(ra, -impulse); rigidbodyB.ApplyImpulse(impulse); rigidbodyB.ApplyTorqueImpulse(rb, impulse); } } } private void PostSeperation(float dt, CollisionPair collisionPair) { var rigidbodyA = collisionPair.m_RigidbodyA; var rigidbodyB = collisionPair.m_RigidbodyB; float mergeFriction = Mathf.Sqrt(rigidbodyA.Friction * rigidbodyB.Friction); for (int i = 0; i < collisionPair.m_NumContacts; ++i) { var contact = collisionPair.m_Contacts[i]; Vector2 ra = contact.m_Point - rigidbodyA.Position; Vector2 rb = contact.m_Point - rigidbodyB.Position; var relativeV = rigidbodyB.GetPointVelocity(rb) - rigidbodyA.GetPointVelocity(ra); var normal = contact.m_Normal; float relativeVN = Vector2.Dot(relativeV, normal); //投影到法向量 //if (relativeVN > 0) //相对速度>0时, 表明没有碰撞趋势了 // return; //Δp = (1 + e) * (v2 - v1) / kMass float deltaPN = (-relativeVN + contact.m_Bias) * contact.m_MassNormal; if (m_AccumulateImpulses) {float lastImpulseN = contact.m_ImpulseNormal; contact.m_ImpulseNormal += deltaPN; //叠加本次冲量(冲量=Δp) if (contact.m_ImpulseNormal <= 0) //防止弹开过程中, 变成拉回来的冲量 { contact.m_ImpulseNormal = 0; deltaPN = -lastImpulseN; } } else { deltaPN = Mathf.Max(deltaPN, 0); //冲量为负, 碰撞后就加速了, 这样不对 } Vector2 impulseN = deltaPN * normal; //转为矢量 rigidbodyA.ApplyImpulse(-impulseN); rigidbodyA.ApplyTorqueImpulse(ra, -impulseN); rigidbodyB.ApplyImpulse(impulseN); rigidbodyB.ApplyTorqueImpulse(rb, impulseN); //摩擦力(切线方向) relativeV = rigidbodyB.GetPointVelocity(rb) - rigidbodyA.GetPointVelocity(ra); //上面的冲量生效后, 再计算相对速度 var tangent = new Vector2(normal.y, -normal.x); //切线(顺时针) float relativeVT = Vector2.Dot(relativeV, tangent); //投影到切线方向 relativeVT = -relativeVT; //取反让值为正(与运动方向相同) float deltaPT = relativeVT * contact.m_MassTangent; if (m_AccumulateImpulses) {float maxDeltaPt = mergeFriction * contact.m_ImpulseNormal; //不能超过法向量的冲量 float lastImpulseT = contact.m_ImpulseTangent; contact.m_ImpulseTangent = Mathf.Clamp(contact.m_ImpulseTangent + deltaPT, -maxDeltaPt, maxDeltaPt); deltaPT = contact.m_ImpulseTangent - lastImpulseT; } else { float maxDeltaPt = mergeFriction * deltaPN; deltaPT = Mathf.Clamp(deltaPT, -maxDeltaPt, maxDeltaPt); } Vector2 impulseT = deltaPT * tangent; rigidbodyA.ApplyImpulse(-impulseT); //摩擦力与运动(趋势)方向相反 rigidbodyA.ApplyTorqueImpulse(ra, -impulseT); rigidbodyB.ApplyImpulse(impulseT); rigidbodyB.ApplyTorqueImpulse(rb, impulseT); } }
参考
2D 游戏物理引擎 - 碰撞约束 - 知乎 (zhihu.com)
从零手写游戏引擎24:物理引擎ResolvePhase - 知乎 (zhihu.com)
标签:穿透,collisionPair,Vector2,float,2d,contact,rigidbodyB,接触面,rigidbodyA From: https://www.cnblogs.com/sailJs/p/17929658.html