PhysX API Basics

Introduction

This chapter covers the basic patterns common to the PhysX application programming interface (API). We are committed to keeping this API stable and backwards-compatible from one minor release to the next, to protect the investment you make in your integration code.

The PhysX API is composed primarily of abstract interface classes. Classes, enumerations and functions defined by the API have the prefix Px.

Note

There is currently one section of the public API which does not have the Px prefix: the PhysX Visual Debugger connection library which has the prefix Pvd.

The PhysX libraries also expose some classes and functions that are not yet an official part of the public API. These can be recognized because they do not have the Px prefix, but they are required to build the PhysX libraries. Even though they are in principle accessible to users, they can be undocumented and we do not guarantee compatibility of these specific classes and functions between PhysX versions.


Memory Management

PhysX performs all allocations via the PxAllocatorCallback interface. You must implement this interface in order to initialize PhysX:

class PxAllocatorCallback
{
public:
    virtual ~PxAllocatorCallback() {}
    virtual void* allocate(size_t size, const char* typeName, const char* filename, int line) = 0;
    virtual void deallocate(void* ptr) = 0;
};

The size of the request is specified in bytes, and PhysX requires that the memory that is returned be 16-byte aligned. On many platforms malloc() returns memory that is 16-byte aligned, and on Windows the system function _aligned_malloc() provides this capability. The other parameters to allocate() are a string which identifies the type of allocation, and the __FILE__ and __LINE__ location inside PhysX code where the allocation was made. Refer to PxAllocatorCallback::allocate() to find out more about them.

A simple implementation of the allocator callback class can be found in the PhysX Extensions library, see class PxDefaultAllocator. This library also contains physx::platformAlignedAlloc() and physx::platformAlignedFree() wrappers for convenient 16-byte aligned memory allocations.

Note

On some platforms PhysX uses system library calls to determine the correct type name, and the system function that returns the type name may call the system memory allocator. If you are instrumenting system memory allocations, you may observe this behavior. To prevent PhysX requesting type names, disable allocation names using the method PxFoundation::setReportAllocationNames().

You can place PhysX objects in memory owned by the application using PhysX’ binary deserialization mechanism. See Serialization for details.


Error Reporting

PhysX logs all error messages through the PxErrorCallback interface. You must implement this interface in order to initialize PhysX:

class UserErrorCallback : public PxErrorCallback
{
public:
    virtual void reportError(PxErrorCode::Enum code, const char* message, const char* file, int line)
    {
        // error processing implementation
        ...
    }
};

There is only a single function to implement, PxErrorCallback::reportError(). This function should log the passed message, or print it on the application’s output console. For the more serious error codes PxErrorCode::eABORT, PxErrorCode::eINVALID_PARAMETER, PxErrorCode::eINVALID_OPERATION, PxErrorCode::eINTERNAL_ERROR and PxErrorCode::eOUT_OF_MEMORY, breaking into the debugger may be a more appropriate choice. Whatever you do, do not just ignore the messages.

A simple implementation of the error callback class can be found in the PhysX Extensions library, see class PxDefaultErrorCallback.


Math Classes

The common math classes used in PhysX are PxVec2, PxVec3, PxVec4, PxMat33, PxMat44, PxTransform, PxQuat and PxPlane, which are are defined in their respective header files, e.g. (SDKRoot)/include/foundation/PxVec3.h. Most of these classes exist for floats and doubles via a typedef. The types support standard operator overloads and typical math operations. Zero and identity objects where appropriate can be constructed by passing the arguments PxZero and PxIdentity respectively.

Some points to note are:

  • PxTransform is a representation of a rigid body transform as a rotation quaternion and a position vector, and PhysX functions which take transforms all use this type.

  • PxPlane is a homogeneous plane equation: that is, the constructor PxPlane(n, d) represents the equation n.x + d = 0.

PxMat33 and PxMat44 matrices represent transformations with basis vectors in the columns (pre-multiply with matrix on the left hand side) and are stored in column-major order. This format is layout compatible with popular graphics APIs such as OpenGL and Direct3D. For example, to set the model transformation for a rigid body in OpenGL:

// retrieve world space transform of rigid body
PxTransform t = rigidActor.getGlobalPose();

// convert to matrix form
PxMat44 m = PxMat44(t);

// set to OpenGL
glMatrixMode(GL_MODELVIEW);
glPushMatrix();

// PxMat44::front() returns a pointer to the first matrix element
glMultMatrixf(m.front());

// draw model

glPopMatrix()

DirectX uses row-major storage for matrices by default (D3DMATRIX), but also stores basis vectors in rows (post-multiply on the right), so PxMat44 may be used in place of D3DXMATRIX types directly.


Connecting PhysX Objects with User Application Objects

Often an application needs to associate PhysX objects with application objects for application logic or rendering purposes. An easy way to connect a single user application object with a PhysX object is to use the userData member provided by the most important PhysX classes (e.g. PxActor::userData, PxShape::userData, PxMaterial::userData, etc). The userData member is a void* pointer which is reserved for application use. Each class only has one userData field, so to manage multiple associations another mechanism must be used.


Type Casting

PhysX API interface classes inherit from a top-level interface called PxBase, which provides mechanisms for type-safe down-casting between interface types. For example, to cast from a PxActor to a PxRigidDynamic, use the following idiom:

PxActor* actor = <...>
PxRigidDynamic* myActor = actor->is<PxRigidDynamic>();

const PxActor* actor = <...>
const PxRigidDynamic* myActor = actor->is<PxRigidDynamic>();

This pattern can be used to cast to intermediate types in the hierarchy such as PxRigidActor, but this is somewhat slower than casting to concrete types. In addition, PxBase provides the following capabilities:

This can be used to cast types in a more cumbersome but faster way, like for example:

PxBase& base = mSceneCollection->getObject(i);
if(base.getConcreteType() == PxConcreteType::eRIGID_DYNAMIC)
{
        PxRigidDynamic& rd = static_cast<PxRigidDynamic&>(base);

Reference Counting

Some PhysX objects are designed to be shared and referenced multiple times in a PhysX scene graph. For example, a PxConvexMesh may be referenced by multiple PxShape objects, each sharing the same geometry but associated with different actors. The ref-counted types inherit from PxRefCounted, which is an interface that manages a reference count. The rules for reference counting are as follows:

  • when an object is created from PxPhysics, it has a reference count of 1.

  • when an object’s reference count reaches 0, the object is destroyed.

  • when a new counted reference is created, the reference count is incremented. Counted references are for example:

  • when a counted reference is destroyed, or the object’s release() method is called, the reference count is decremented.

  • when an object is created through deserialization, its reference count is 1, plus the number of counted references that exist to the object.

The initial reference count of 1 ensures the object is not destroyed until the application allows it by calling release() - thereafter it will be destroyed when no remaining counted references to it exist.

For example, if you create a shape using PxPhysics::createShape() and attach it to an actor with PxRigidActor::attachShape(), it has a reference count of 2. If you then call the shape’s release() method, it has a reference count of 1. When the actor is destroyed, or the shape is detached from the actor, the reference count is decremented, and since it is now 0, the shape is destroyed.

The PxRefCounted::acquireReference() method increments the reference count of an object. For example, when a spatial query returns a reference to a mesh shape, and you want to pass that result to another thread for deferred processing, incrementing the reference count will ensure that even if the shape referencing the mesh is released, the mesh continues to exist.

Note

subtypes of PxGeometry do not have counted references to the meshes to which they point, e.g. when PxConvexMeshGeometry points to a PxConvexMesh. A counted reference exists only when the geometry is within a PxShape.

Note

shapes are often created using the utility method PxRigidActorExt::createExclusiveShape(). Take special care when deserializing such actors (see Shapes and Reference Counting of Deserialized Objects)


Using Different Units

PhysX is designed to produce correct results regardless of the units of length or mass, so long as inputs use those units consistently. However, there are certain tolerances values whose defaults need to be adjusted depending on the units. In order to ensure that these tolerances default to reasonable values, adjust the values in PxTolerancesScale when creating the PxPhysics and PxCookingParams interfaces. Tolerances for objects are set at creation time, and may then be overridden by the application.

You should set tolerances based on the typical size of objects in your simulation. For example, if you are working with objects of size approximately one meter, but in units of centimeters, you should set the scale as follows:

PxFoundation* foundation = ...;
PxTolerancesScale scale;
scale.length = 100;        // typical length of an object
scale.speed = 981;         // typical speed of an object, gravity*1s is a reasonable choice
PxPhysics* p = PxCreatePhysics(PX_PHYSICS_VERSION, *foundation, scale, ...);

This will result in the defaults for values like PxShape::contactDistance being scaled appropriately for your objects.

You can also set the typical object mass in PxTolerancesScale.

It is important to use the same PxTolerancesScale value for initialization of PxCookingParams and PxPhysics, and also when creating PxSceneDesc objects.


Determinism

Given a specific platform and PhysX build, PhysX will produce identical simulation results for scenes with rigid bodies and articulations. In order to achieve this determinism, the application must create a new PxScene instance and recreate the scene with the exact same sequence of API calls. Note that removing all the objects from a scene is generally not sufficient to reinitialize it for this purpose. For determinism the application must also use a consistent time-stepping scheme and ensure the same responses from simulation callbacks that modify data.

Simulation results can vary between platforms due to differences in hardware floating point precision and differences in how the compiler reorders instructions during optimization. This means that behavior can be different between different platforms, different compilers operating on the same platform or between optimized and unoptimized builds using the same compiler on the same platform.

In addition, the PhysX simulation can produce divergent behavior if any conditions or settings in the simulation were changed, although behavior and determinism is not sensitive to the number of CPU worker threads used.

PhysX does currently not guarantee determinism for any scene that contains non-rigid actors such as PxPBDParticleSystem, PxDeformableSurface or PxDeformableVolume.

Enhanced Determinism

Even the addition or removal of a single actor that is not interacting with the existing set of actors in the scene can produce divergent results because it may change the order in which constraints are processed. PhysX provides a mechanism to overcome the issue of divergent behavior in such cases where additional actors being added or actors being removed from the scene do not interact with the other actors in the scene. This mechanism can be enabled by raising PxSceneFlag::eENABLE_ENHANCED_DETERMINISM on PxSceneDesc::flags prior to creating the scene. Enabling this mode makes some performance concessions to be able to offer an improved level of determinism. The application must still follow all the requirements to achieve deterministic behavior described previously in order for this mechanism to produce consistent results.