For my CSC 572 final project, I implemented a 3D rigid-body physics engine. Naturally, the problem of n-body collisions arises and we need to figure out what to do about them. Here are notes I wrote about my implementation.
Particles are spheres. They essentially contain the following information:
- moment of inertia
- position vector
- velocity vector
- rotation matrix
- angular velocity vector (right-hand rule, scaled to angular velocity in radians)
Contacts essentially contain the following information:
- a reference to the two particles, A and B. A and B both contain references to the contact, as well.
- the position where it happened
- the contact normal , which is a vector from B to A, with length equal to the penetration depth
- a state value that says if this contact is stable or not
Every loop, we calculate our list of contacts. This, along with gravity calculation, are the most computationally expensive operations simply due to it being operations, so they are offloaded onto the GPU.
Calculating our list of contacts
This is a operation that just checks if two particles are intersecting. If they are, it pushes a new contact onto the list
contacts. We are currently working on parallelizing it on the GPU.
n-body contact solving
Once we have our list of contacts
contacts, to take care of the n-body case, we essentially do the following in pseudo code:
repeat contacts.size() times: for contact in contacts: contact.solve_momentum()
contact.solve_momentum() takes care of the 2-body case described here.
Solving contacts multiple times takes care of cases like the Newton’s Cradle. This may seem inefficient in theory because if there’s particles, there might be contacts and thus calls for
solve_momentum(). However, in practice, most particles spend most of their time floating around freely so momentum calculations can be safely done on the CPU, and this step ends up only taking <10% of total processing time.
Suppose you have a bunch of balls in a line like this:
Velocity | > .... Balls | o oooo
. are right, left, and zero respectively. We expect this situation to end up like this:
.... > oooo o
So, by running it iteratively, we can propagate the momentum through the chain:
Before > .... o oooo 1. >.... ooooo 2. .>... ooooo 3. ..>.. ooooo 4. ...>. ooooo 5. ....> ooooo After .... > oooo o
The number of iterations could be tuned, or calculated in a smarter way (I was thinking, maybe calculate contacts within contiguous groups that are all touching each other to reduce the number of iterations) but it works well enough for our purposes.
If two balls are leaving each other, or if they have low relative velocity/the contact is marked as “stable,” then there is no momentum or friction to be applied.
The 3D momentum problem gets reduced to a 1D momentum problem along the collision normal . So, we use as our velocity and plug it into the conservation of momentum equation with restitution derived here.
From the final momenta, we derive the impulse . Multiplying by some arbitrary coefficient approximates the normal force , giving us the magnitude of friction where is the coefficient of friction.
Next, we need to calculate the relative surface velocity of the particles. Suppose we were only looking at a single particle. The contact point is essentially undergoing two movements:
- it is moving at , the particle’s translational velocity
- it is moving along the particle’s surface due to rotation, or
So, the surface velocity at the contact point of a single particle is . The relative surface velocity is just the difference between the two particles’ surface velocities at the contact point.
However, friction can only happen tangentially to the normal, so the direction of friction is actually in the direction of (its orthogonal component). Normalize this, and multiply by the magnitude derived earlier, and we have our final friction.