Advanced Rigid Body Topics

Continuous Collision Detection

When continuous collision detection (or CCD) is turned on, the affected rigid bodies will not go through other objects at high velocities (a problem also known as tunneling). To enable CCD, three things need to be happen:

  1. CCD needs to be turned on at scene level:

    PxPhysics* physx;
    ...
    PxSceneDesc desc;
    desc.flags |= PxSceneFlag::eENABLE_CCD;
    ...
    
  2. Pairwise CCD needs to be enabled in the pair filter:

    static PxFilterFlags filterShader(
            PxFilterObjectAttributes attributes0,
            PxFilterData filterData0,
            PxFilterObjectAttributes attributes1,
            PxFilterData filterData1,
            PxPairFlags& pairFlags,
            const void* constantBlock,
            PxU32 constantBlockSize)
    {
            pairFlags = PxPairFlag::eRESOLVE_CONTACTS;
            pairFlags |= PxPairFlag::eCCD_LINEAR;
            return PxFilterFlags();
    }
    
    ...
    
    desc.filterShader       = testCCDFilterShader;
    physx->createScene(desc);
    
  3. CCD need to be enabled for each PxRigidBody that requires CCD:

    PxRigidBody* body;
    ...
    body->setRigidBodyFlag(PxRigidBodyFlag::eENABLE_CCD, true);
    

Once enabled, CCD only activates between shapes whose relative speeds are above the sum of their respective CCD velocity thresholds. These velocity thresholds are automatically calculated based on the shape's properties and support non-uniform scales.

Contact Notification and Modification

CCD supports the full set of contact notification events that are supported with the discrete collision detection. For details on contact notification, see the documentation for Callbacks.

CCD supports contact modification. To listen to these modify callbacks, derive from the class PxCCDContactModifyCallback:

class MyCCDContactModification : public PxCCDContactModifyCallback
{
        ...
        void onCCDContactModify(PxContactModifyPair* const pairs, PxU32 count);
};

And then implement the function onContactModify of PxContactModifyCallback:

void MyContactModification::onContactModify(PxContactModifyPair *const pairs, PxU32 count)
{
        for(PxU32 i=0; i<count; i++)
        {
                ...
        }
}

This onContactModify callback operates using the same semantics as the discrete collision detection contact modification callbacks. For further details, please refer to the documentation on Callbacks.

As with discrete collision detection, CCD will only emit contact modification events for a given pair if the user has specified the pair flag PxPairFlag::eMODIFY_CONTACTS in the filter shader.

Triggers

Currently, shapes flagged with PxShapeFlag::eTRIGGER_SHAPE will not be included in CCD. However, it is possible to get trigger events from CCD by using not flagging trigger shapes as PxShapeFlag::eTRIGGER_SHAPE and instead configuring the filter shaders to return the following state for pairs involving trigger shapes:

pairFlags = PxPairFlag::eTRIGGER_DEFAULT | PxPairFlag::eCCD_LINEAR; return PxFilterFlag::eDEFAULT;

It should be noted that not flagging shapes as PxShapeFlag::eTRIGGER_SHAPE can result in the triggers being more expensive. Therefore, this workaround should be reserved for use only in situations where important trigger events will be missed without CCD.

Tuning CCD

The CCD should generally work without any tuning. However, there are 4 properties that can be adjusted:

  1. PxSceneDesc.ccdMaxPasses: This variable controls the number of CCD passes we perform. This is defaulted to 1, meaning that all objects are attempted to be updated to the TOI of their first contact. Any remaining time after the TOI of their first contact will be dropped. Increasing this value permits the CCD to run multiple passes. This reduces the likelihood of time being dropped but can increase the cost of the CCD.
  2. PxRigidBody::setMinCCDAdvanceCoefficient(PxReal advanceCoefficient): This method allows you to adjust the amount by which the CCD advances objects in a given pass. By default, this value is 0.15, meaning that CCD will advance an object by the 0.15 * ccdThreshold, where ccdThreshold is a value computed per-shape that acts as a lower-bound of the maximum amount of time that could be consumed that before there is a chance that the object could have tunneled. The default value of 0.15 improves the fluidity of motion without risking missed collisions. Reducing this value can negatively impact fluidity but will reduce the likelihood of objects clipping at the end of a frame. Increasing this value may increase the likelihood of objects tunneling. This value should only be set in the range [0,1].
  3. Enabling the flag PxSceneFlag::eDISABLE_CCD_RESWEEP on the PxSceneDesc.flags: Enabling this flag disables CCD resweeps. This can result in missed collisions as the result of ricochets but has the potential to reduce the overhead of the CCD. In general, enabling this advancement mode still guarantees that objects will not pass through the static environment but no longer guarantees that dynamic objects with CCD enabled will not pass through each-other.
  4. PxRigidBody::setRigidBodyFlag(PxRigidBodyFlag::eENABLE_CCD_FRICTION, true): Enabling this flag enables the application of friction forces in the CCD. This is disabled by default. As the CCD operates using only linear motion, enabling friction inside CCD can cause visual artifacts.

Performance Implications

Enabling CCD on a scene/all bodies in a scene should be relatively efficient in PhysX 3.3 but it will have some performance impact even when all the objects in the scene are moving relatively slowly. A great deal of effort has been put into optimizing the CCD and as a result, this additional overhead should only constitute a very small portion of the overall simulation time when the objects are moving slowly. As the objects' velocities increase, the CCD overhead will increase, especially if there are a lot of high-speed objects in close proximity. Increasing the number of CCD passes can make the CCD more expensive although the CCD will terminate early if the additional passes aren't required.

Limitations

The CCD system is a best-effort conservative advancement scheme. It runs a finite number of CCD substeps (currently 2) and drops any remaining time. Usually, time is only dropped on high-speed objects at the moment of impact so it is not noticeable. However, this artifact can become noticeable if you simulate an object that is sufficiently small/thin relative to the simulation time-step that the object could tunnel if it was accelerated by gravity from rest for 1 frame, i.e. a paper-thin rigid body. Such an object would always be moving at above its CCD velocity threshold and could result in a large proportion of simulation time being dropped for that object and any objects in the same island as it (any objects whose bounds overlap the bounds of that object). This could cause a noticeable slow-down/stuttering effect caused by the objects in that island becoming noticeably out-of-sync with the rest of the simulation. It is therefore recommended that paper-thin/tiny objects should be avoided if possible.

It is also recommended that you filter away CCD interactions between bodies that are constrained together, e.g. limbs in the same ragdoll. Allowing CCD interactions between limbs of the same ragdoll could increase the cost of CCD and also potentially cause time to be dropped unnecessarily. CCD interactions are automatically disabled between links in an articulation.

Articulations

An articulation is a single actor comprising a set of links (each of which behaves like a rigid body) connected together with special joints. Every articulation has a tree-like structure - so there can be no loops or breaks.

Articulations are a somewhat experimental feature of the SDK, under active research. Currently their primary use is modeling physically actuated characters. They support higher mass ratios, more accurate drive models, have better dynamic stability and a more robust recovery from joint separation than standard PhysX joints. However, they are considerably more expensive - the CPU budget on XBox360 for the collision and dynamics of a 20-link articulation is in the region of 0.5 CPU-milliseconds.

Although articulations do not directly build on joints, they use very similar configuration mechanisms. We assume in this section that you are already familiar with using PhysX joints.

Creating an Articulation

To create an articulation, first create the articulation actor without links:

PxArticulation *articulation = physics.createArticulation();

Then add links one by one, each time specifying a parent link (NULL for the parent of the initial link), and the pose of the new link:

PxsArticulationLink* link = articulation->createLink(parent, linkPose);
link->createShape(linkGeometry, material);
PxRigidBodyExt::updateMassAndInertia(*link, 1.0f);

Articulation links have a restricted subset of the functionality of rigid bodies. They may not be kinematic, and they do not support damping, velocity clamping, or contact force thresholds. Sleep state and solver iteration counts are properties of the entire articulation rather than the individual links.

Each time a link is created beyond the first, a PxArticulationJoint is created between it and its parent. Specify the joint frames for each joint, in exactly the same way as for a PxJoint:

PxArticulationJoint *joint = link->getInboundJoint();
joint->setParentPose(parentAttachment);
joint->setChildPose(childAttachment);

Finally, add the articulation to the scene:

scene.addArticulation(articulation);

Articulation Joints

The only form of articulation joint currently supported is an anatomical joint, whose properties are similar to D6 joint configured for a typical rag doll. Specifically, the joint is a spherical joint, with angular drive, a twist limit around the child joint frame's x-axis, and an elliptical swing cone limit around the parent joint frame's x-axis. The configuration of these properties is very similar to a D6 or spherical joint, but the options provided are slightly different.

The swing limit is a hard elliptical cone limit which does not support spring or restitution from movement perpendicular to the limit surface. You can set the limit ellipse angle as follows:

joint->setSwingLimit(yAngle, zAngle);

fot the limit angles around y and z. Unlike the PxJoint cone limit the limit provides a tangential spring to limit movement of the axis along the limit surface. Once configured, enable the swing limit:

joint->setSwingLimitEnabled(true);

The twist limit allows configuration of upper and lower angles:

joint->setTwistLimit(lower, upper);

and again you must explicitly enable it:

joint->setTwistLimitEnabled(true);

As usual with joint limits, it is good practice to use a sufficient limit contactDistance value that the solver will start to enforce the limit before the limit threshold is exceeded.

Articulation joints are not breakable, and it is not possible to retrieve the constraint force applied at the joint.

Driving an Articulation

Articulations are driven through joint acceleration springs. You can set an orientation target, an angular velocity target, and spring and damping parameters that control how strongly the joint drives towards the target. You can also set compliance values, indicating how strongly a joint resists acceleration. A compliance near zero indicates very strong resistance, and a compliance of 1 indicates no resistance.

Articulations are driven in two phases. First the joint spring forces are applied (we use the term internal forces for these) and then any external forces such as gravity and contact forces. You may supply different compliance values for at each joint for each phase.

Note that with joint acceleration springs, the required strength of the spring is estimated using just the mass of the two bodies connected by the joint. By contrast, articulation drive springs account for the masses of all the bodies in the articulation, and any stiffness from actuation at other joints. This estimation is an iterative process, controlled using the externalDriveIterations and internalDriveIterations properties of the PxArticulation class.

Articulation Projection

When any of the joints in an articulation separate beyond a specified threshold, the articulation is projected back together automatically. Projection is an iterative process, and the PxArticulation attributes separationThreshold and projectionIterations control when projection occurs and trade cost for robustness.

Articulations and Sleeping

Like rigid dynamic objects, articulations are also put into a sleep state if their energy falls below a certain threshold for a period of time. In general, all the points in the section Sleeping in Rigid Body Dynamics apply to articulations as well. The main difference is that articulations can only go to sleep if each individual articulation link fulfills the sleep criteria.

Substepping

You may want the simulation frequency of physx to be higher than the frame rate of your application, to allow for higher fidelity simulation or better stability. The simplest way to do this is just to call simulate() and fetchResults() multiple times:

for(PxU32 i=0; i<substepCount; i++)
{
        ... pre-simulation work (update controllers, etc) ...
        scene->simulate(substepSize);
        scene->fetchResults(true);
        ... post simulation work (process physics events, etc) ...
}

The code in Samples/SampleBase/SampleStepper.cpp in the sample framework demonstrates a different approach using completion tasks.

Using Completion Tasks

If you submit a completion task to the scene in the simulate() call, the simulation will decrement its reference count when simulation completes, which (assuming there are no outstanding references) will cause the task to run. The completion task first calls fetchResults and performs any per-substep work:

mScene->fetchResults(true);
mSample->onSubstep(mSubStepSize);

Since a task may not submit itself as a completion to simulate(), the completion tasks are double buffered. To start another simulation step, the completion task registers the other task with the task manager (which also sets the reference count to 1), calls simulate() again if necessary:

StepperTask &s = ownerTask == &mCompletion0 ? mCompletion1 : mCompletion0;
s.setContinuation(*mScene->getTaskManager(), NULL);
mScene->simulate(mSubStepSize, &s);

Finally, it releases the reference which prevents the new completion task from executing:

s.removeReference();

Synchronizing with Other Threads

An important consideration for substepping is that simulate() and fetchResults() are classed as write calls on the scene, and it is therefore illegal to read from or write to a scene while those functions are running. PhysX does not lock its scene graph, but it will report an error in the checked build if it detects that multiple threads make concurrent calls to the same scene, unless they are all read calls.

To synchronize with the rendering thread the sample stepper holds an extra reference to the first completion task until the renderDone() method is invoked, so that the renderer can safely read the scene in parallel with simulation. On completion of all substeps, the stepper signals a synchronization object which may be checked with a wait() method.

Custom Constraints

Constraint is a more general term for joints. Constraints use shaders for the same reason as contact filtering: There is a requirement to inject performance sensitive custom code into the SDK. While joints were native objects of the PhysX 2.x API, PhysX 3.0 only supports a fully customizeable constraint object in the core API, and all 2.x joint types are implemented using this mechanism as extensions. Let us take a short look at how this works. Once the reader understands, he will be in a good position to create his own joint types. You should read the chapter on joints before you try to understand their workings, however.

When you call PxJointCreate(), the extensions library first fills out a PxConstraintDesc object, which is a bunch of parameters for constraint creation. Here is the code for a spherical joint:

PxConstraintDesc nxDesc;
nxDesc.actor[0]                         = desc.actor[0];
nxDesc.actor[1]                         = desc.actor[1];
nxDesc.flags                            = desc.constraintFlags;
nxDesc.linearBreakImpulse       = desc.breakForce;
nxDesc.angularBreakImpulse      = desc.breakTorque;

nxDesc.solverPrep                       = SphericalJointSolverPrep;
nxDesc.project                          = SphericalJointProject;
nxDesc.visualize                        = SphericalJointVisualize;

nxDesc.dataSize                         = sizeof(SphericalJointData);
nxDesc.connector                        = joint->getConnector();

The first few settings are self explanatory ... like the actors to connect, when the joint should break, and so on. The next three are three callback functions -- user defined shaders. (See the section on filter shaders to find out what shaders are, and the rules that apply to them.) They contain the code that mathematically defines the behavior of the joint. Every time the joint needs to be solved, the simulation will call these functions.

Finally, the 'connector' is a class of additional user defined joint specific functionality that are not called from the solver directly, and are not shaders.

Lastly, the filled out descriptor is used to create the constraint object:

PxConstraint* constraint = physics.createConstraint(nxDesc);