Shapes and Geometries

NVIDIA PhysX SDK

Shapes and Geometries

Shapes

Shapes describe the spatial extent and collision properties of actors. A PxShape is owned by the PxActor through which it was created, and is released along with the actor. Each shape contains a PxGeometry object and a reference to a PxMaterial, which must both be specified upon creation. A shape may also have a transform relative to its parent actor. To create an actor with several shapes (sometimes referred to as a compound), just call PxRigidActor::createShape() several times. There are no persistence requirements on the PxGeometry instance that is passed as a function argument in PxRigidActor::createShape(). The PhysX SDK takes a copy of the PxGeometry object rather than maintaining a reference to it.

There are some restrictions on the geometry types that may be specified for a shape, depending on the type of the parent actor. Actors of type PxRigidStatic may have any kind of supported shape; that is, sphere, capsule, box, convex mesh, triangle mesh, plane or heightfield. The shapes permitted for actors of type PxRigidDynamic depends on whether or not the actor is kinematic. PxRigidDynamic actors that have been set up as kinematic may be given any of the supported shapes, just as with PxRigidStatic actors. Dynamic actors, on the other hand, will not accept heightfields or triangle meshes or planes. Actors of type PxArticulationLink have the same restrictions as non-kinematic PxRigidDynamic instances in that they are also forbidden to accept heightfields and triangle meshes and planes. To complete the discussion it is worth noting that actors of type PxCloth, PxParticleFluid and PxParticleSystem do not accept shapes at all because their collison is handled by systems special to these actor types.

The PhysX SDK supports contacts between all possible combinations of shape pair except for combinations where both shapes are a plane or a heightfield or a triangle mesh. The collision of two meshes, for example, is not supported. Similarly, collision between a plane and a heightfield or between two heightfields remains unsupported by the sdk.

Simulation Shapes and Scene Query Shapes

PxShape instances are configured by default to participate in the intersection tests that determine the contacting features of shape pairs. They are also configured by default to participate in scene query tests. It is possible to configure PxShape instances to participate or not in both types of test. This can be done before or after the shape's actor has been added to the scene.

The following pseudo-code configures a PxShape instance so that it no longer participates in shape pair intersection tests:

void disableShapeInShapePairIntersectionTests(PxShape* shape)
{
    shape->setFlag(PxShapeFlag::eSIMULATION_SHAPE,false);
}

A PxShape instance can be configured to participate in shape pair intersection tests as follows:

void enableShapeInShapePairIntersectionTests(PxShape* shape)
{
    shape->setFlag(PxShapeFlag::eSIMULATION_SHAPE,true);
}

To disable a PxShape instance from scene query tests:

void disableShapeInSceneQueryTests(PxShape* shape)
{
    shape->setFlag(PxShapeFlag::eSCENE_QUERY_SHAPE,false);
}

Finally, a PxShape instance can be re-enabled in scene query tests:

void enableShapeInSceneQueryTests(PxShape* shape)
{
    shape->setFlag(PxShapeFlag::eSCENE_QUERY_SHAPE,true);
}

Trigger Shapes

PxShape instances can be configured as trigger shapes. Trigger shapes play no part in the simulation of the scene (though they can be configured to participate in scene queries). Instead, their role is to report that there has been an overlap with another shape. This does not involve generating the contact features of the intersection. As a result, contact reports are not available for trigger shapes. A further point to note is that because trigger points play no part in the simulation it makes no sense for the eSIMULATION_SHAPE and eTRIGGER_SHAPE flags to be simultaneously raised. To avoid any ambiguity the sdk will act to prevent these flags being simultaneously raised; that is, if the eSIMULATION_SHAPE(eTRIGGER_SHAPE) flag is raised then attempts to raise the eTRIGGER_SHAPE(eSIMULATION_SHAPE) flag will be rejected. When this occurs an error is passed to the error stream.

Trigger shapes have been used in SampleSubmarine to determine if the submarine has reached the treasure. In the following code the PxActor representing the treasure has its solitary shape configured as a trigger shapes:

PxShape* treasureShape;
gTreasureActor->getShapes(&treasureShape, 1);
treasureShape->setFlag(PxShapeFlag::eSIMULATION_SHAPE, false);
treasureShape->setFlag(PxShapeFlag::eTRIGGER_SHAPE, true);

The overlaps with trigger shapes are reported in SampleSubmarine through the implementation of PxSimulationEventCallback::onTrigger in the PxSampleSubmarine class, a sub-class of PxSimulationEventCallback:

void SampleSubmarine::onTrigger(PxTriggerPair* pairs, PxU32 count)
{
    for(PxU32 i=0; i < count; i++)
    {
        // ignore pairs when shapes have been deleted
        if (pairs[i].flags & (PxTriggerPairFlag::eDELETED_SHAPE_TRIGGER | PxTriggerPairFlag::eDELETED_SHAPE_OTHER))
            continue;

        if((&pairs[i].otherShape->getActor() == mSubmarineActor) && (&pairs[i].triggerShape->getActor() == gTreasureActor))
        {
            gTreasureFound = true;
        }
    }
}

The code above iterates through all pairs of overlapping shapes that involve a trigger shape. If it is found that the treasure has been touched by the submarine then the flag gTreasureFound is set true.

Kinematic triangle meshes (planes, heighfields)

It is possible to create a kinematic PxRigidDynamic which can have a triangle mesh (plane, heighfield) shape. If this shape has a simulation shape flag, this actor must stay kinematic. If you change the flag to not simulated, you can switch even the kinematic flag.

To setup kinematic triangle mesh see following code:

PxRigidDynamic* meshActor = getPhysics().createRigidDynamic(PxTransform::createIdentity());
PxShape* meshShape;
if(meshActor)
{
        meshActor->setRigidDynamicFlag(PxRigidDynamicFlag::eKINEMATIC, true);

        PxTriangleMeshGeometry triGeom;
        triGeom.triangleMesh = triangleMesh;
        meshShape = meshActor->createShape(triGeom, defaultMaterial);
        getScene().addActor(*meshActor);
}

To switch a kinematic triangle mesh actor to a dynamic actor:

PxRigidDynamic* meshActor = getPhysics().createRigidDynamic(PxTransform::createIdentity());
PxShape* meshShape;
if(meshActor)
{
        meshActor->setRigidDynamicFlag(PxRigidDynamicFlag::eKINEMATIC, true);

        PxTriangleMeshGeometry triGeom;
        triGeom.triangleMesh = triangleMesh;
        meshShape = meshActor->createShape(triGeom, defaultMaterial);
        getScene().addActor(*meshActor);

        PxConvexMeshGeometry convexGeom = PxConvexMeshGeometry(convexBox);
        convexShape = meshActor->createShape(convexGeom,defaultMaterial);
        convexShape->setFlag(PxShapeFlag::eSIMULATION_SHAPE, false);
}

// ... now switch to dynamic

meshShape->setFlag(PxShapeFlag::eSIMULATION_SHAPE, false);
convexShape->setFlag(PxShapeFlag::eSIMULATION_SHAPE, true);
meshActor->setRigidDynamicFlag(PxRigidDynamicFlag::eKINEMATIC, false);

Geometries

The PxGeometry class defines a volume or surface with a fixed position and orientation. Typically, as when used in a shape or scene query, a transform specifies the frame in which the geometry is interpreted.

For bulk objects, such as a convex mesh, triangle mesh or height field, PhysX allows multiple PxGeometry objects to refer to a single mesh or height field, and supports per-instance scaling.

Note

Each mesh (or height field) is reference counted, and the reference count refers to the number of PxShapes whose geometries reference the mesh, rather than the number of PxGeometry objects.

Spheres

A PxSphereGeometry is specified by one attribute, its radius, and is centered at the origin.

Capsules

A PxCapsuleGeometry is centered at the origin. It is specified by a radius and a half-height value by which its axis extends along the positive and negative X-axis.

To create a dynamic actor whose geometry is a capsule standing upright, the shape needs a relative transform that rotates it around the Z-axis by a quarter-circle. By doing this, the capsule will extend along the Y-axis of the actor instead of the X-axis. Setting up the shape and actor is otherwise the same as for the sphere:

PxRigidDynamic* aCapsuleActor = thePhysics->createRigidDynamic(PxTransform(position));
PxTransform relativePose(PxQuat(PxHalfPi, PxVec(0,0,1)));
PxShape* aCapsuleShape = aCapsuleActor->createShape(PxCapsuleGeometry(radius, halfHeight), aMaterial, relativePose);
PxRigidBodyExt::updateMassAndInertia(*aCapsuleActor, capsuleDensity);
aScene->addActor(aCapsuleActor);

The function PxTransformFromSegment() converts from a line segment defining the capsule axis to a transform and halfheight.

Boxes

A PxBoxGeometry has three attributes, the three extents halved:

PxShape* aBoxShape = aBoxActor->createShape(PxBoxGeometry(a/2, b/2, c/2), aMaterial);

Where a, b and c are the side lengths of the resulting box.

Planes

Planes divide space into "above" and "below" them. Everything "below" the plane will collide with it.

The Plane lies on the YZ plane with "above" pointing towards positive X. To convert from a plane equation to an equivalent transform, use the function PxTransformFromPlaneEquation(). PxPlaneEquationFromTransform() performs the reverse conversion.

A PxPlaneGeometry has no attributes, since the shape's pose entirely defines the plane's collision volume.

Shapes with a PxPlaneGeometry may only be created for static actors.

Convex Meshes

Creating a PxConvexMesh requires cooking. It is assumed here that the cooking library has already been initialized (see Startup and Shutdown.)

The following steps explain how to create a simple square pyramid.

First, define the vertices of the convex object:

static const PxVec3 convexVerts[] = {PxVec3(0,1,0),PxVec3(1,0,0),PxVec3(-1,0,0),PxVec3(0,0,1),PxVec3(0,0,-1)};

Then construct a description of the convex data layout:

PxConvexMeshDesc convexDesc;
convexDesc.points.count     = 5;
convexDesc.points.stride    = sizeof(PxVec3);
convexDesc.points.data      = convexVerts;
convexDesc.flags            = PxConvexFlag::eCOMPUTE_CONVEX;

Now use the cooking library to construct a PxConvexMesh:

PxToolkit::MemoryOutputStream buf;
if(!cooking.cookConvexMesh(convexDesc, buf))
    return NULL;
PxToolkit::MemoryInputData input(buf.getData(), buf.getSize());
PxConvexMesh* convexMesh = thePhysics->createConvexMesh(input);

Finally, create a shape using a PxConvexMeshGeometry which instances the mesh:

PxShape* aConvexShape = aConvexActor->createShape(PxConvexMeshGeometry(convexMesh), aMaterial);

A user can optionally provide a per-instance PxMeshScale in the PxConvexMeshGeometry. The default scale is the identity.

The default convex hull generation code is selected when using the PxConvexFlag::eCOMPUTE_CONVEX flag alone. The algorithm tries to create a convex hull as close to the source vertices as possible. This can sometimes fail when the source data is geometrically challenging, for example if it contains a lot of vertices close to each-other, etc. An error is reported to the error stream in case of failure. If this happens, the best option is to switch to an alternative hull generation routine using the PxConvexFlag::eCOMPUTE_CONVEX|PxConvexFlag::eINFLATE_CONVEX flags, both together. This allows the code to inflate the source data by a margin - defined by PxCookingParams::skinWidth -, which gives the code more freedom to correct the problematic geometry. Alternatively it is possible for users to provide an already created hull, by filling up both PxConvexMeshDesc::points and PxConvexMeshDesc::triangles, and omitting the PxConvexFlag::eCOMPUTE_CONVEX flag. Some checks are still performed to make sure the provided hull is valid, so the cooking call can still fail at that point.

In any case the number of vertices and the number of convex polygons in the final cooked hull are both limited to 256.

Height Fields

As the name suggests, terrains can be described by just the height values on a regular, rectangular sampling grid:

PxHeightFieldSample* samples = (PxHeightFieldSample*)alloc(sizeof(PxHeightFieldSample)*(numRows*numCols));

Each sample consists of a 16 bit integer height value, two materials (for the two triangles in the samples rectangle) and a tesselation flag. The flag and materials refer to the cell below and to the right of the sample point, and indicate along which diagonal to split it into triangles, and the materials of those triangles. A special predefined material PxHeightFieldMaterial::eHOLE specifies a hole in the height field. See the reference documentation for PxHeightFieldSample for more details.

To tell the system the number of sampled heights in each direction, use a descriptor to instantiate a PxHeightField object:

PxHeightFieldDesc hfDesc;
hfDesc.format             = PxHeightFieldFormat::eS16_TM;
hfDesc.nbColumns          = numCols;
hfDesc.nbRows             = numRows;
hfDesc.samples.data       = samples;
hfDesc.samples.stride     = sizeof(PxHeightFieldSample);

PxHeightField* aHeightField = thePhysics->createHeightField(hfDesc);

Now create a PxHeightFieldGeometry and a shape:

PxHeightFieldGeometry hfGeom(aHeightField, PxMeshGeometryFlags(), heightScale, rowScale, colScale);
PxShape* aHeightFieldShape = aHeightFieldActor->createShape(hfGeom, aMaterialArray, nbMaterials);

The row and column scales tell the system how far apart the sampled points lie in the associated direction. The height scale scales the integer height values to a floating point range.

The variant of createShape() used here specifies an array of materials for the height field, which will be indexed by the material indices of each cell to resolve collisions with that cell. The single-material variant of createShape() may be used instead, but the height field material indices must all be a single value or the special value eHOLE.

Triangle Meshes

Creating a PxTriangleMesh requires cooking. It is assumed here that the cooking library has already been initialized (see Startup and Shutdown.)

Like graphical triangle meshes, a collision triangle mesh consists of a collection of vertices and the triangle indices. Triangle mesh creation requires use of the cooking library:

PxTriangleMeshDesc meshDesc;
meshDesc.points.count           = nbVerts;
meshDesc.points.stride          = sizeof(PxVec3);
meshDesc.points.data            = verts;

meshDesc.triangles.count        = triCount;
meshDesc.triangles.stride       = 3*sizeof(PxU32);
meshDesc.triangles.data         = indices32;

PxToolkit::MemoryOutputStream writeBuffer;
bool status = cooking.cookTriangleMesh(meshDesc, writeBuffer);
if(!status)
    return NULL;

PxToolkit::MemoryInputData readBuffer(writeBuffer.getData(), writeBuffer.getSize());
return physics.createTriangleMesh(readBuffer);

Indices can be 16 or 32 bit. The strides used here assume that vertices and indices are arrays of PxVec3s and 32bit integers respectively with no gaps in the data layout.

Shapes with triangle mesh geometries may only be created for static and kinematic actors:

PxShape* aTriMeshShape = aTriMeshActor->createShape(PxTriangleMeshGeometry(triangleMesh), aMaterial);

The user can optionally specify a per-instance PxMeshScale in the PxTriangleMeshGeometry. The default scale is the identity.

Like height fields, triangle meshes support per-triangle material indices. To use per-triangle materials for a mesh, provide the indices to the cooking library in the mesh descriptor, and use the multi-material form of createShape().

Mesh Scaling

A shared PxTriangleMesh or PxConvexMesh may be stretched or compressed when it is instanced by a geometry. This allows multiple instancing of the same mesh with different scale factors applied. Scaling is specified with the PxMeshScale class, which defines scale factors to be applied along 3 orthogonal axes. A factor greater than 1.0 results in stretching, while a factor less than 1.0 results in compression. The directions of the axes are governed by a quaternion, and specified in the local frame of the shape.

The following code creates a shape with a PxTriangleMesh scaled by a factor of x along the x-axis, y along the y-axis, and z along the z-axis:

// created earlier
PxRigidActor* myActor;
PxTriangleMesh* myTriMesh;
PxMaterial* myMaterial;

// create a shape instancing a triangle mesh at the given scale
PxMeshScale scale(PxVec3(x,y,z), PxQuat::createIdentity());
PxTriangleMeshGeometry geom(myTriMesh,scale);
PxShape* myTriMeshShape = myActor->createShape(geom,*myMaterial);

Convex meshes are scaled using the PxMeshScale class in a similar manner. The following code creates a shape with a PxConvexMesh scaled by a factor of x along (sqrt(1/2), 1.0, -sqrt(1/2)), by a factor of y along (0,1,0) and a by a factor of z along (sqrt(1/2), 1.0, sqrt(1/2)):

PxMeshScale scale(PxVec3(x,y,z), PxQuat quat(PxPi*0.25f, PxVec3(0,1,0)));
PxConvexMeshGeometry geom(myTriMesh,scale);
PxShape* myConvexMeshShape = myActor->createShape(geom,*myMaterial);

Height fields can also be scaled, using scale factors stored in PxHeightFieldGeometry. In this case the scale is assumed to be along the axes of the rows, columns and height directions of the height field. The scaling of is demonstrated in SampleNorthPole in SampleNorthPoleBuilder.cpp:

PxHeightFieldGeometry hfGeom(heightField, PxMeshGeometryFlags(), heightScale, hfScale, hfScale);
PxShape* hfShape = hfActor->createShape(hfGeom, getDefaultMaterial());

In this example, the coordinates along the x and z axes are scaled by hfScale, while the sample heights are scaled by heightScale.

PxGeometryHolder

When a geometry is provided for a shape, either on creation or with PxShape::setGeometry(), the geometry is copied into the SDK's internal structures. If you know the type of a shape's geometry you may retrieve it directly:

PxBoxGeometry boxGeom;
bool status = shape->getBoxGeometry(geometry);

The status return code is set to false if the shape's geometry is not of the expected type.

However, it is often convenient to retrieve a geometry object from a shape without first knowing its type - for example, to call a function which takes a PxGeometry reference as an argument.

PxGeometryHolder is a union-like class that allows the return of a PxGeometry object by value, regardless of type. Its use is illustrated in the createRenderObjectFromShape() function in PhysXSample.cpp:

PxGeometryHolder geom = shape->getGeometry();

switch(geom.getType())
{
case PxGeometryType::eSPHERE:
    shapeRenderActor = SAMPLE_NEW(RenderSphereActor)(renderer, geom.sphere().radius);
    break;
case PxGeometryType::eCAPSULE:
    shapeRenderActor = SAMPLE_NEW(RenderCapsuleActor)(renderer, geom.capsule().radius, geom.capsule().halfHeight);
    break;
    ...
}

The function PxGeometryHolder::any() returns a reference to a PxGeometry object. For example, to compare two shapes in a scene for overlap:

bool testForOverlap(const PxShape& s0, const PxShape& s1)
{
    return PxGeometryQuery::overlap(s0.getGeometry().any(), PxShapeExt::getGlobalPose(s0),
                                    s1.getGeometry().any(), PxShapeExt::getGlobalPose(s1));
}

Detaching Shapes

In the North Pole Sample, some of the shapes detach on contact with a snowball. To request notification of this event, the sample sets a flag in the simulation filter function:

if (needsContactReport(filterData0, filterData1))
{
    pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;
}

When this flag is set for a pair, the initial collision of that pair will generate a callback through PxSimulationEventCallback::onContact(). The implementation of this callback in the sample simply records which detachable shapes were touched during simulation. needsContactReport() is a helper function which returns true if one of the shapes is detachable and the other is marked as a snowball. It determines this by testing the flags in each shape's simulationFilterData, which were set on shape creation using the setDetachable() and setSnowball() functions. Collision filtering is discussed in more detail in the Callbacks and Customization section.

After simulation, the sample iterates over the list of touched detachable shapes and detaches each one from its owning actor. Since a PxShape must belong to a PxActor, the sample creates a new actor, whose global pose is that of the original shape. The geometry and material for the new shape are retrieved from the original shape:

PxRigidDynamic* newActor = mPhysics->createRigidDynamic(pose);

PxMaterial* mat;
shape->getMaterials(&mat,1);
PxGeometryHolder geometry = shape->getGeometry();
PxTransform newActorPose = PxShapeExt::getGlobalPose(*shape);

PxRigidDynamic* newActor = PxCreateDynamic(*mPhysics, newActorPose, geometry.any(), *mat, 1);
shape->release();

Shape Vertex and Triangle Queries

Convex meshes, triangle meshes, and height fields can all be queried for vertex and face data. This is particularly useful, for example, when rendering the mesh of the convex shape. The function:

RenderBaseActor* PhysXSample::createRenderObjectFromShape(PxShape* shape, RenderMaterial* material)

in PhysXSample.cpp contains a switch statement with a case for each shape type, illustrating the steps required to query the vertices and faces.

It is possible to get information about triangle from a triangle mesh using the getTriangle function, You may also get the information about adjacency triangles for the triangle, for this you need to cook your triangle meshes with cooking parameter buildTriangleAdjacencies, otherwise the adjacency information is not created and stored.

Convex Meshes

A convex mesh contains an array of vertices, an array of faces, and an index buffer which concatenates the vertex indices for each face. To unpack a convex mesh, the first step is to extract the shared convex mesh:

PxConvexMesh* convexMesh = geom.convexMesh().convexMesh;

Then obtain references to the vertex and index buffers:

PxU32 nbVerts = convexMesh->getNbVertices();
const PxVec3* convexVerts = convexMesh->getVertices();
const PxU8* indexBuffer = convexMesh->getIndexBuffer();

Now iterate over the array of faces to triangulate them:

PxU32 offset = 0;
for(PxU32 i=0;i<nbPolygons;i++)
{
    PxHullPolygon face;
    bool status = convexMesh->getPolygonData(i, face);
    PX_ASSERT(status);

    const PxU8* faceIndices = indexBuffer + face.mIndexBase;
    for(PxU32 j=0;j<face.mNbVerts;j++)
    {
        vertices[offset+j] = convexVerts[faceIndices[j]];
        normals[offset+j] = PxVec3(face.mPlane[0], face.mPlane[1], face.mPlane[2]);
    }

    for(PxU32 j=2;j<face.mNbVerts;j++)
    {
        *triangles++ = PxU16(offset);
        *triangles++ = PxU16(offset+j);
        *triangles++ = PxU16(offset+j-1);
    }

    offset += face.mNbVerts;
}

Observe that the vertex indices of the polygon begin at indexBuffer[face.mIndexBase], and the count of vertices is given by face.mNbVerts.

Triangle Meshes

Triangle meshes contain arrays of vertices and index triplets which define the triangles by indexing into the vertex buffer. The arrays can be accessed directly from the shared triangle mesh:

PxTriangleMesh* tm = geom.triangleMesh().triangleMesh;
const PxU32 nbVerts = tm->getNbVertices();
const PxVec3* verts = tm->getVertices();
const PxU32 nbTris = tm->getNbTriangles();
const void* tris = tm->getTriangles();

The indices may be stored with either 16-bit or 32-bit values, specified when the mesh was originally cooked. To determine the storage format at runtime, use the API call:

const bool has16bitIndices = tm->has16BitTriangleIndices();

Assuming that the triangle indices are stored in 16-bit format, find the jth vertex of the ith triangle by:

const PxU16* triIndices = (const PxU16*)tris;
const PxU16 index = triIndices[3*i +j];

The corresponding vertex is:

const PxVec3& vertex = verts[index];

Height Fields

The storage of height field data is platform-dependent, and therefore direct access to the height field samples is not provided. Instead, calls are provided to render the samples to a user-supplied buffer.

Again, the first step is to retrieve the geometry for the height field:

const PxHeightFieldGeometry& geometry = geom.heightField();

The height field has three scaling parameters:

const PxReal    rs = geometry.rowScale;
const PxReal    hs = geometry.heightScale;
const PxReal    cs = geometry.columnScale;

And a shared data structure, which stores the row and column count:

PxHeightField*  hf = geometry.heightField;
const PxU32     nbCols = hf->getNbColumns();
const PxU32     nbRows = hf->getNbRows();

To render the height field, first extract the samples to an array:

const PxU32 nbVerts = nbRows * nbCols;
PxHeightFieldSample* sampleBuffer = new PxHeightFieldSample[nbVerts];
hf->saveCells(sampleBuffer, nbVerts * sizeof(PxHeightFieldSample));

The samples are stored in row-major order; that is, row0 is stored first, followed by row1, then row2, and so on. Thus the sample corresponding to the ith row and the jth column is i*nbCols + j.

Evaluate the scaled vertices of the height field as follows:

PxVec3* vertices = new PxVec3[nbVerts];
for(PxU32 i = 0; i < nbRows; i++)
{
    for(PxU32 j = 0; j < nbCols; j++)
    {
        vertices[i * nbCols + j] = PxVec3(PxReal(i) * rs, PxReal(sampleBuffer[j + (i*nbCols)].height) * hs, PxReal(j) * cs);
    }
}

Then tessellate the field from the samples as required.