Callbacks and Customization

In this chapter we will look at a number of callback functions the SDK provides that let the user listen to simulation events and even customize some parts of the simulation. The callback functions are implemented as member functions of classes which the user is expected to subclass, providing custom implementations for the functions. This is the same mechanism that we already explained in the chapter basics, to let the user provide custom allocators and error notification.

Simulation Events

The simplest type of simulation callbacks are the events. The application may simply listen in on these without needing to react in any way. There is only one restriction on the code you may put in the callbacks: Any SDK state change from them is disallowed. This may be a bit surprising given that normally the SDK is set up to be double buffered, and writes are permitted while the simulation is running in the background, as any new state is written to an inactive state backbuffer. However, these event callbacks are not called from within the simulation thread, but rather from inside fetchResults(). At that point some buffered operations have already been processed, so the situation is more fragile. All write operations to the scene should be buffered and carried out after fetchResults() returns.

Inside fetchResults() among other things we 'swap the buffers'. This means that we copy the objects' simulation results to their API-visible state. Some event callbacks happen before this swap, and some after. The events that happen before are:

  • onTrigger
  • onContact
  • onConstraintBreak

When these events get received, the shapes, actors, etc. will still be in the state they were in before the simulation ran. This is preferable, because these events were detected early on during the simulation, before objects were integrated (moved) forward. For example, a pair of shapes that get an onContact() to report that they are in contact will still be in contact when the call is made, even though after fetchResults() returns, they may have bounced apart again.

On the other hand, these events are sent after the swap:

  • onSleep
  • onWake

Sleep information is updated after objects have been integrated, so it makes sense to send these events after the swap.

You 'listen' to any of these events by doing two things: First, define a callback function by subclassing PxSimulationEventCallback. Not all of its member functions have to be implemented, only the ones to be overwritten. For constraint break events, this is the only thing that needs to be done. For sleep and wake events, you must raise the flag PxActorFlag::eSEND_SLEEP_NOTIFIES on all actors from which notifies are desired. To get onContact and onTrigger events, in the filter shader callback, set a flag for all pairs of interacting objects for which you wish to receive these events. See the section on using collision filtering below for details.

Here is an example for a contact event function from SampleSubmarine:

void SampleSubmarine::onContact(const PxContactPairHeader& pairHeader, const PxContactPair* pairs, PxU32 nbPairs)
{
        for(PxU32 i=0; i < nbPairs; i++)
        {
                const PxContactPair& cp = pairs[i];

                if(cp.events & PxPairFlag::eNOTIFY_TOUCH_FOUND)
                {
                        if((pairHeader.actors[0] == mSubmarineActor) || (pairHeader.actors[1] == mSubmarineActor))
                        {
                                PxActor* otherActor = (mSubmarineActor == pairHeader.actors[0]) ?
                                        pairHeader.actors[1] : pairHeader.actors[0];
                                Seamine* mine =  reinterpret_cast<Seamine*>(otherActor->userData);
                                // insert only once
                                if(std::find(mMinesToExplode.begin(), mMinesToExplode.end(), mine) == mMinesToExplode.end())
                                        mMinesToExplode.push_back(mine);

                                break;
                        }
                }
        }
}

SampleSubmarine is a subclass of PxSimulationEventCallback. onContact receives the pair for which the requested contact events have been triggered. The above function is only interested in eNOTIFY_TOUCH_FOUND events, which are raised whenever two shapes start to touch. In fact it is only interested in touch events of the submarine -- which is checked in the second if-statement. It then goes on to assume that the second actor is a mine (which works in this example because the sample is configured such that no other contact reports will get sent when a submarine actor is involved). After that, it adds the mine to a set of mines that should explode during the next update.

Note

By default collisions between kinematic rigid bodies and kinematic and static rigid bodies will not get reported. To enable these reports raise the PxSceneFlag::eENABLE_KINEMATIC_PAIRS or ::eENABLE_KINEMATIC_STATIC_PAIRS flag respectively by calling PxScene::setFlag().

Frequently, users are only interested in contact reports, if the force of impact is larger than a certain threshold. This allows to reduce the amount of reported pairs which need to get processed. To take advantage of this option the following additional configurations are necessary:

  • Use PxPairFlag::eNOTIFY_THRESHOLD_FORCE_FOUND, ::eNOTIFY_THRESHOLD_FORCE_PERSISTS, ::eNOTIFY_THRESHOLD_FORCE_LOST instead of ::eNOTIFY_TOUCH_FOUND etc.
  • Specify the threshold force for a dynamic rigid body through PxRigidDynamic::setContactReportThreshold(). If the body collides with an other object and the contact force is above the threshold, a report will get sent (if enabled according to the PxPairFlag setting of the pair). If two colliding dynamic bodies both have a force threshold specified then the lower threshold will be used.

Note

If a dynamic rigid body collides with multiple static objects, then the impact force of all those contacts will get summed up and used to compare against the force threshold. In other words, even if the impact force against each individual static object is below the threshold, the contact reports will still get sent for each pair if the sum of those forces exceeds the threshold.

Extracting Contact information

The onContact simulation event permits read-only access to all contact points for a given PxContactPair. In previous releases, these were available as a flattened array of PxContactPoint objects. However, PhysX 3.3 introduces a new format for this data: the compressed contact stream. The contact information is now compressed into an appropriate format for a given PxContactPair depending on certain properties, e.g. depending on the shapes involved, the properties of the contacts, materials and whether the contacts are modifiable.

As there are a large number of combinations of different formats, the user is provided with two built-in mechanisms to access the contact data. The first approach provides a mechanism to extract contacts from a user buffer and can be used as below:

void MySimulationCallback::onContact(const PxContactPairHeader& pairHeader, const PxContactPair* pairs, PxU32 nbPairs)
{
        const PxU32 bufferSize = 64;
        PxContactPairPoint contacts[bufferSize];
        for(PxU32 i=0; i < nbPairs; i++)
        {
                const PxContactPair& cp = pairs[i];

                PxU32 nbContacts = pairs[i].extractContacts(contacts, bufferSize);
                for(PxU32 j=0; j < nbContacts; j++)
                {
                        PxVec3 point = contacts[j].position;
                        PxVec3 impulse = contacts[j].impulse;
                        PxU32 internalFaceIndex0 = contacts[j].internalFaceIndex0;
                        PxU32 internalFaceIndex1 = contacts[j].internalFaceIndex1;
                        //...
                }
        }
}

This approach requires copying data to a temporary buffer in order to access it. The second approach allows the user to iterate over the contact information without extracting their own copy:

void MySimulationCallback::onContact(const PxContactPairHeader& pairHeader, const PxContactPair* pairs, PxU32 nbPairs)
{
        for(PxU32 i=0; i < nbPairs; i++)
        {
                const PxContactPair& cp = pairs[i];

                const PxU8* stream = cp.contactStream;

                PxContactStreamIterator iter((PxU8*)stream, cp.contactStreamSize);

                //Contact forces can be found starting at the next 16-byte boundary following the contact stream end
                stream += ((cp.contactStreamSize + 15) & ~15);
                const PxReal* impulses = reinterpret_cast<const PxReal*>(stream);

                PxU32 flippedContacts = (cp.flags & PxContactPairFlag::eINTERNAL_CONTACTS_ARE_FLIPPED);
                PxU32 hasImpulses = (cp.flags & PxContactPairFlag::eINTERNAL_HAS_IMPULSES);
                PxU32 nbContacts = 0;

                while(iter.hasNextPatch())
                {
                        iter.nextPatch();
                        while(iter.hasNextContact())
                        {
                                iter.nextContact();
                                PxVec3 point = iter.getContactPoint();
                                PxVec3 impulse = hasImpulses ? dst.normal * impulses[nbContacts] : PxVec3(0.f);

                                PxU32 internalFaceIndex0 = flippedContacts ? iter.getFaceIndex1() : iter.getFaceIndex0();
                                PxU32 internalFaceIndex1 = flippedContacts ? iter.getFaceIndex0() : iter.getFaceIndex1();
                                //...
                                nbContacts++;
                        }
                }
        }
}

This approach is slightly more involved because it requires the user to not only iterate over all of the data but also consider conditions like whether the pair has been flipped or whether impulses have been reported with the pair. However, this approach of iterating over the data in-place may be more efficient because it doesn't require copying data.

Collision Filtering

In almost all applications beyond the trivial, the need arises to exempt certain pairs of objects from interacting, or to configure the SDK collision detection behavior in a particular way for an interacting pair. In the submarine sample, like indicated above, we need to be notified when the submarine touched a mine, or the chain of a mine, so that we can have them blow up. The crab's AI also needs to know when crabs touch the heightfield.

Before we can understand what the sample does to achieve this, we need to understand the possibilities of the SDK filtering system. Because filtering potentially interacting pairs happens in the deepest parts of the simulation engine, and needs to be applied to all pairs of objects that come near each other, it is particularly performance sensitive. The simplest way to implement it would be to always call a callback function to each potentially interacting pair, where the application, based on the two object pointers could determine, using some custom logic -- like consulting its game data base -- whether the pair should interact. Unfortunately this quickly becomes too slow if done for a very large game world, especially if the collision detection processing happens on a remote processor like the GPU or an other kind of vector processor with local memory, which would have to suspend its parallel computations, interrupt the main processor that runs game code, and have it execute the callback before it can continue. Even if it were to be executed on a CPU, it would likely be done so simultaneously on multiple cores or hyperthreads, and thread safe code would have to be put in place to make sure that concurrent access to shared data is safe. Far better is to use some kind of fixed function logic that can execute on the remote processor. This is what we did in PhysX 2.x -- unfortunately the simple group based filtering rules we provided were not flexible enough to cover all applications. In 3.0, we introduce both a shader system, which lets the developer implement an arbitrary system of rules using code that runs on the vector processor (and is therefore not able to access any eventual game data base in main memory), which is more flexible than 2.x fixed function filtering, but just as efficient, and a totally flexible callback mechanism where the filter shader calls a CPU callback function that is able to access any application data, at the cost of performance -- see PxSimulationFilterCallback for details. The best part is that an application can decide on a per-pair basis to make this speed vs. flexibility tradeoff.

Let us look at the shader system first: Here is the filter shader implemented by SampleSubmarine:

PxFilterFlags SampleSubmarineFilterShader(
        PxFilterObjectAttributes attributes0, PxFilterData filterData0,
        PxFilterObjectAttributes attributes1, PxFilterData filterData1,
        PxPairFlags& pairFlags, const void* constantBlock, PxU32 constantBlockSize)
{
        // let triggers through
        if(PxFilterObjectIsTrigger(attributes0) || PxFilterObjectIsTrigger(attributes1))
        {
                pairFlags = PxPairFlag::eTRIGGER_DEFAULT;
                return PxFilterFlag::eDEFAULT;
        }
        // generate contacts for all that were not filtered above
        pairFlags = PxPairFlag::eCONTACT_DEFAULT;

        // trigger the contact callback for pairs (A,B) where
        // the filtermask of A contains the ID of B and vice versa.
        if((filterData0.word0 & filterData1.word1) && (filterData1.word0 & filterData0.word1))
                pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;

        return PxFilterFlag::eDEFAULT;
}

SampleSubmarineFilterShader is a simple shader function that is an implementation of the PxSimulationFilterShader prototype declared in PxFiltering.h. The shader filter function (called SampleSubmarineFilterShader above) may not reference any memory other than arguments of the function and its own local stack variables -- because the function may be compiled and executed on a remote processor.

SampleSubmarineFilterShader() will be called for all pairs of shapes that come near each other -- more precisely: for all pairs of shapes whose axis aligned bounding boxes in world space are found to intersect for the first time. All behavior beyond that is determined by what SampleSubmarineFilterShader() returns.

The arguments of SampleSubmarineFilterShader() include PxFilterObjectAttributes and PxFilterData for the two objects, and a constant block of memory. Note that the pointers to the two objects are NOT passed, because those pointers refer to the computer's main memory, and that may, as we said, not be available to the shader, so the pointers would not be very useful, as dereferencing them would likely cause a crash. PxFilterObjectAttributes and PxFilterData are intended to contain all the useful information that one could quickly glean from the pointers. PxFilterObjectAttributes are 32 bits of data, that encode the type of object: For example PxFilterObjectType::eRIGID_STATIC, ::eRIGID_DYNAMIC, or even ::ePARTICLE_SYSTEM. Additionally, it lets you find out if the object is kinematic, or a trigger.

Each PxShape and PxParticleBase object in PhysX has a member variable of type PxFilterData. This is 128 bits of user defined data that can be used to store application specific information related to collision filtering. This is the other variable that is passed to SampleSubmarineFilterShader() for each object.

There is also the constant block. This is a chunk of per-scene global information that the application can give to the shader to operate on. You will want to use this to encode rules about what to filter and what not.

Finally, SampleSubmarineFilterShader() also has a PxPairFlags parameter. This is an output, like the return value PxFilterFlags, though used slightly differently. PxFilterFlags tells the SDK if it should ignore the pair for good (eKILL), ignore the pair while it is overlapping, but ask again, when filtering related data changes for one of the objects (eSUPPRESS), or call the low performance but more flexible CPU callback if the shader cannot decide (eCALLBACK).

PxPairFlags specifies additional flags that stand for actions that the simulation should take in the future for this pair. For example, eNOTIFY_TOUCH_FOUND means notify the user when the pair really starts to touch, not just potentially.

Let us look at what the above shader does:

// let triggers through
if(PxFilterObjectIsTrigger(attributes0) || PxFilterObjectIsTrigger(attributes1))
{
        pairFlags = PxPairFlag::eTRIGGER_DEFAULT;
        return PxFilterFlag::eDEFAULT;
}

This means that if either object is a trigger, then perform default trigger behavior (notify the application about start and end of touch), and otherwise perform 'default' collision detection between them.

// generate contacts for all that were not filtered above
pairFlags = PxPairFlag::eCONTACT_DEFAULT;

// trigger the contact callback for pairs (A,B) where
// the filtermask of A contains the ID of B and vice versa.
if((filterData0.word0 & filterData1.word1) && (filterData1.word0 & filterData0.word1))
        pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;

return PxFilterFlag::eDEFAULT;

This says that for all other objects, perform 'default' collision handling. In addition, there is a rule based on the filterDatas that determines particular pairs where we ask for touch notifications. To understand what this means, we need to know the special meaning that the sample gives to the filterDatas.

The needs of the sample are very basic, so we will use a very simple scheme to take care of it. The sample first gives named codes to the different object types using a custom enumeration:

struct FilterGroup
{
        enum Enum
        {
                eSUBMARINE     = (1 << 0),
                eMINE_HEAD     = (1 << 1),
                eMINE_LINK     = (1 << 2),
                eCRAB          = (1 << 3),
                eHEIGHTFIELD   = (1 << 4),
        };
};

The sample identifies each shape's type by assigning its PxFilterData::word0 to this FilterGroup type. Then, it puts a bit mask that specifies each type of object that should generate a report when touched by an object of type word0 into word1. This could be done in the samples whenever a shape is created, but because shape creation is a bit encapsulated in SampleBase, it is done after the fact, using this function:

void setupFiltering(PxRigidActor* actor, PxU32 filterGroup, PxU32 filterMask)
{
        PxFilterData filterData;
        filterData.word0 = filterGroup; // word0 = own ID
        filterData.word1 = filterMask;  // word1 = ID mask to filter pairs that trigger a contact callback;
        const PxU32 numShapes = actor->getNbShapes();
        PxShape** shapes = (PxShape**)SAMPLE_ALLOC(sizeof(PxShape*)*numShapes);
        actor->getShapes(shapes, numShapes);
        for(PxU32 i = 0; i < numShapes; i++)
        {
                PxShape* shape = shapes[i];
                shape->setSimulationFilterData(filterData);
        }
        SAMPLE_FREE(shapes);
}

This sets up the PxFilterDatas of each shape belonging to the passed actor. Here are some examples how this is used in SampleSubmarine:

setupFiltering(mSubmarineActor, FilterGroup::eSUBMARINE, FilterGroup::eMINE_HEAD | FilterGroup::eMINE_LINK);
setupFiltering(link, FilterGroup::eMINE_LINK, FilterGroup::eSUBMARINE);
setupFiltering(mineHead, FilterGroup::eMINE_HEAD, FilterGroup::eSUBMARINE);

setupFiltering(heightField, FilterGroup::eHEIGHTFIELD, FilterGroup::eCRAB);
setupFiltering(mCrabBody, FilterGroup::eCRAB, FilterGroup::eHEIGHTFIELD);

This scheme is probably too simplistic to use in a real game, but it shows the basic usage of the filter shader, and it will ensure that SampleSubmarine::onContact() is called for all interesting pairs.

An alternative group based filtering mechanism is provided with source in the extensions function PxDefaultSimulationFilterShader. And, again, if this shader based system is too inflexible, consider using the callback approach provided with PxSimulationFilterCallback.

Contact Modification

Sometimes users would like to have special contact behavior. For example to implement sticky contacts, give objects the appearance of floating or swimming inside each other, or making objects go through apparent holes in walls. A simple approach to achieve such effects is to let the user change the properties of contacts after they have been generated by collision detection, but before the contact solver. Because both of these steps occur within the scene simulate() function, a callback must be used.

The callback occurs for all pairs of colliding shapes for which the user has specified the pair flag PxPairFlag::eMODIFY_CONTACTS in the filter shader.

To listen to these modify callbacks, derive from the class PxContactModifyCallback:

class MyContactModification : public PxContactModifyCallback
        {
        ...
        void onContactModify(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++)
        {
                ...
        }
}

Basically, every pair of shapes comes with an array of contact points, that have a number of properties that can be modified, such as position, contact normal, and separation. For the time being, friction properties of the contacts cannot be modified. See PxModifiableContactPoint and PxContactSet for properties that can be modified.

In addition to modifying contact properties, it is possible to adjust mass and inertia scales separately. This can be used to tune how contacts between a pair of bodies affect the bodies linear and angular velocity respectively. See PxModifiableContactPoint and PxContactSet for further information.

There are a couple of special requirements for the callback due to the fact that it is coming from deep inside the SDK. In particular, the callback should be thread safe and reentrant. In other words, the SDK may call onContactModify() from any thread and it may be called concurrently (i.e., asked to process sets of contact modification pairs simultaneously).

The contact modification callback can be set using the contactModifyCallback member of PxSceneDesc or the setContactModifyCallback() method of PxScene.

Active Transforms

The active transforms API provides an efficient way to reflect actor transform changes in a PhysX scene to an associated external object such as a render mesh.

When a scene's fetchResults() method is called an array of PxActiveTransform structs is generated, each entry in the array contains a pointer to the actor that moved, its user data and its new transform. Because only actors that have moved will be included in the list this approach is potentially much more efficient than, for example, analyzing each actor in the scene individually.

The example below shows how to use active transforms to update a render object:

// update scene
scene.simulate(dt);
scene.fetchResults();

// retrieve array of actors that moved
PxU32 nbActiveTransforms;
PxActiveTransform* activeTransforms = scene.getActiveTransforms(nbActiveTransforms);

// update each render object with the new transform
for (PxU32 i=0; i < nbActiveTransforms; ++i)
{
        MyRenderObject* renderObject = static_cast<MyRenderObject*>(activeTransforms[i].userData);
        renderObject->setTransform(activeTransforms[i].actor2World);
}

Note

PxSceneFlag::eENABLE_ACTIVETRANSFORMS must be set on the scene for the active transforms array to be generated.

Table Of Contents

Previous topic

Geometry Queries

Next topic

Aggregates