Hey guys, first timer here.. I posted this question on the Bullet forums, but still haven't heard a reply. I'm slightly freaking out given that I've spent the entire semester on this project and am STILL stuck on the same things. I can't even tell what I'm doing wrong, so I was hoping you could look over my general outline and tell me where I'm messing up. I'll post code if that helps too, maybe my problem is in the calculations.. Side note: you can recommend papers, tutorials, guides, GDC talks, but I can almost guarantee I've seen it already.
Main Physics Update
- Update positions/rotations
- Update velocity (add external forces, eg. gravity)
- Collision detection
- Collision resolution
- Add stored impulses to velocities, reset impulses, dampen velocities
I think I've got this part right, my biggest question here is if I'm treating the angular velocity properly. The collision resolution treats body.angularVelocity as a linear vector (simply adds rotations to it), however my final rotation works by converting body.angularVelocity to a quaternion and multiplying the body.quaternion by it.
Collision Detection
- Broadphase detection
- GJK+EPA
- Add/update contacts to the contact manifold
I believe my collision detection works, but the most important question is: is what I'm returning the same thing that SI/PGS is expecting? GJK+EPA returns a normal, a penetration depth, and 1 contact point: essentially EPA finishes with a support point and a triangle in the minkowski configuration. Two contacts can be found by first retrieving the bayesian coefficients (u, v, w) with respect to the last projected point, and multiplying those coefficients with the associated support points:
contact.pointA = triangle.v1.support_a*u + triangle.v2.support_a*v + triangle.v3.support_a*w
contact.pointB = triangle.v1.support_b*u + triangle.v2.support_b*v + triangle.v3.support_b*w
contact.point = (contact.pointA + contact.pointB) * 0.5
Where our contact point is simply the midpoint of contact.pointA and contact.pointB. The new contact is added to the bodyA/bodyB manifold. If the same contact already exists (the pair of associated vertices for each support point {support_a.vertex_index, support_b.vertex_index} is the same in an existing contact) then that contact is updated with the new information. Otherwise the new contact is added. If our manifold exceeds 4 points then delete the weakest contact.
Since GJK+EPA only returns 1 contact point, I'm maintaining the other contact points by updating their penetration depth:
contact.depth = contact.normal.dot( bodyA.point(contact.point.support_a) - bodyB.point(contact.point.support_b) )
Then if the new depth < 0 (no longer penetrating) or depth > 1.0 (penetrating too far, probably fell through) simply delete the contact.
Collision Resolution
- For each contact: solve penetrations, warmstart impulses, calculate b for baumgarte stabilization
- For each contact: For each iteration: find delta lambda and add impulse
Solve Penetrations
contact.rA = contact.pointA - bodyA.position
contact.tangent = contact.rA.cross(contact.normal)
bodyA.d = bodyA.invMass + contact.normal.dot( contact.tangent.cross(contact.rA) * bodyA.invInertia )
contact.d = 1.0 / (bodyA.d + bodyB.d)
dVA = contact.normal.dot( bodyA.velocity ) + contact.tangent.dot( bodyA.angularVelocity )
immediateLinearImpulse = contact.normal * body.invMass * (contact.depth - (dVA * contact.d) - (dVB * contact.d))
immediateAngularImpulse = contact.tangent * body.invInertia * (contact.depth - (dVA * contact.d) - (dVB * contact.d))
both of the immediate impulses are directly added to each body's linear/angular velocity. I feel iffy about simply adding the angular impulse, and am not sure if I'm doing this right. Also I'm treating invInertia as a scalar value (0.5) rather than a matrix, I hope this is okay?
Warmstart Impulses
Dampen contact's stored impulse and apply to each body
contact.impulse *= warmstart // warmstart ~0.8
body.applyLinearImpulse( contact.normal * body.invMass * contact.impulse )
body.applyAngularImpulse( contact.tangent * body.invInertia * contact.impulse )
Note that the impulses don't affect the velocity until after the collision resolution.
Baumgarte Stabilization
contact.b = -baumgarte * JDotV * dt // baumgarte ~0.2
Sequential Impulses
M = (bodyA.invMass, bodyA.invInertia, bodyB.invMass, bodyB.invInertia)
B = J*M
JDotA = J.dot(B) * contact.impulse
D = J.dot(B)
delta = (contact.b - JDotB) / D
contact.impulse = clamp(contact.impulse + delta, 0, Infinity)
body.applyLinearImpulse( contact.normal * body.invMass * delta )
body.applyAngularImpulse( contact.tangent * body.invInertia * delta )
Note that contact.impulse is stored for the next time around, and the body's impulses are reset by the end of the physics engine update. Again I'm unsure about adding the angular impulse like this, and also treating the invInertia as a scalar. But more importantly, I'm not sure that my sequential solver is correct at all..
I could really use some advice, if anybody here knows what I'm doing wrong or if I misunderstood something please let me know.
Videos
With sequential impulses
Physics engine with PGS (sequential impulses) enabled, with 10 iterations. The results are....bad.
Without sequential impulses
The cube falls right through the floor
No resting contact, and completely bizzare behaviour with cubes over the edge