Shapes and Geometries

Shapes

Shapes describe the spatial extent and collision properties of actors. They are used for three purposes within PhysX: intersection tests that determine the contacting features of rigid objects, scene query tests such as raycasts, and defining trigger volumes that generate notifications when other shapes intersect with them.

Shapes are reference counted, see Reference Counting.

Each shape contains a PxGeometry object and a reference to a PxMaterial, which must both be specified upon creation. The following code creates a shape with a sphere geometry and a specific material:

PxShape* shape = physics.createShape(PxSphereGeometry(1.0f), myMaterial, false);
myActor.attachShape(*shape);
shape->release();

The method PxRigidActor::createShape() is equivalent to the three lines above.

Note

for reference counting behavior of deserialized shapes refer to Reference Counting of Deserialized Objects.

The parameter 'false' to createShape() informs the SDK that the shape will not be shared with other actors. You can use shape sharing to reduce the memory costs of your simulation when you have many actors with identical geometry, but shared shapes have a very strong restriction: you cannot update the attributes of a shared shape while it is attached to an actor.

Optionally you may configure a shape by specifying shape flags of type PxShapeFlags. By default a shape is configured as

  • a simulation shape (enabled for contact generation during simulation)
  • a scene query shape (enabled for scene queries)
  • being visualized if debug rendering is enabled

When a geometry object is specified for a shape, the geometry object is copied into the shape. There are some restrictions on which geometries may be specified for a shape, depending on the shape flags and the type of the parent actors.

  • TriangleMesh, HeightField and Plane geometries are not supported for simulation shapes that are attached to dynamic actors, unless the dynamic actors are configured to be kinematic.
  • TriangleMesh and HeightField geometries are not supported for trigger shapes.

See the following sections for more details.

Detach the shape from the actor as follows:

myActor.detachShape(*shape);

Note

in previous versions of PhysX, release() was used to detach a shape from its actor and destroy it. This use of release() is deprecated in PhysX 3.3 and will not be supported in futures version of PhysX.

Simulation Shapes and Scene Query Shapes

Shapes may be independently configured to participate in either or both of scene queries and contact tests. By default, a shape will participate in both.

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

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

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

void enableShapeInContactTests(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);
}

Note

If the movement of the shape's actor does not need to be controlled by the simulation at all, i.e., the shape is used for scene queries only and gets moved manually if necessary, then memory can be saved by additionally disabling simulation on the actor (see the API documentation on PxActorFlag::eDISABLE_SIMULATION).

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. Contacts are not generated for the intersection, and as a result contact reports are not available for trigger shapes. Further, because triggers play no part in the simulation, the sdk will not allow the the eSIMULATION_SHAPE eTRIGGER_SHAPE flags to be raised simultaneously; that is, if one flag is raised then attempts to raise the other will be rejected, and an error will be 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(1.0f));
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(1.0f));
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);
aCapsuleShape->setLocalPose(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;
convexDesc.vertexLimit      = 256;

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, PxConvexMeshDesc::indices and PxConvexMeshDesc::polygons (or you can let the polygons compute using computeHullPolygons if you only have vertices and triangles, PxConvexMeshDesc::triangles is marked as deprecated and will be removed in the future, though it can still be used instead of polygons), 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. For an example please see PxConvexMesh* PxToolkit::createConvexMeshSafe.

In any case the number of vertices and the number of convex polygons in the final cooked hull are both limited to 256. It is possible to limit the maximum vertex count in the created hull by setting the vertexLimit in convex descriptor. Please note that if limit is used together with inflate flag, the resulting number of vertices can exceed the maximum number of vertices because the inflate algorithm adds vertices to solve sharp edges problem, see also PxToolkit::createConvexMeshSafe().

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(1.0f));
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 or height field using PxMeshQuery::getTriangle function. You can also retrieve adjacent triangle indices for the given triangle (triangle triangleNeighbour[i] shares the edge vertex[i]-vertex[(i+1)%3] with triangle indexed as 'triangleIndex', where vertex is in the range from 0 to 2). To enable this feature the triangle mesh is cooked with buildTriangleAdjacencies parameter set to true.

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.

Heightfield modification

Heightfield samples can be modified at runtime in rectangular blocks. In the following code snippet we create a HF and modify it's samples:

// create a 5x5 HF with height 100 and materials 2,3
PxHeightFieldSample samples1[25];
for (PxU32 i = 0; i < 25; i ++)
{
        samples1[i].height = 100;
        samples1[i].materialIndex0 = 2;
        samples1[i].materialIndex1 = 3;
}

PxHeightFieldDesc heightFieldDesc;
heightFieldDesc.nbColumns = 5;
heightFieldDesc.nbRows = 5;
heightFieldDesc.thickness = -10;
heightFieldDesc.convexEdgeThreshold = 3;
heightFieldDesc.samples.data    = samples1;
heightFieldDesc.samples.stride  = sizeof(PxHeightFieldSample);

PxPhysics* physics = getPhysics();
PxHeightField* pHeightField = physics->createHeightField(heightFieldDesc);

// create modified HF samples, this 10-sample strip will be used as a modified row
// Source samples that are out of range of target heightfield will be clipped with no error.
PxHeightFieldSample samplesM[10];
for (PxU32 i = 0; i < 10; i ++)
{
        samplesM[i].height = 1000;
        samplesM[i].materialIndex0 = 1;
        samplesM[i].materialIndex1 = 127;
}

PxHeightFieldDesc desc10Rows;
desc10Rows.nbColumns = 1;
desc10Rows.nbRows = 10;
desc10Rows.samples.data = samplesM;
desc10Rows.samples.stride       = sizeof(PxHeightFieldSample);

pHeightField->modifySamples(1, 0, desc10Rows); // modify row 1 with new sample data

PhysX does not keep a mapping from the heightfield to heightfield shapes that reference it. Call PxShape::setGeometry on each shape which references the height field, to ensure that internal data structures are updated to reflect the new geometry:

PxShape *hfShape = userGetHfShape(); // the user is responsible for keeping track of shapes associated with modified HF
hfShape->setGeometry(PxHeightFieldGeometry(pHeightField, ...));

Please also note that PxShape::setGeometry() does not guarantee correct/continuous behavior when objects are resting on top of old or new geometry.