Character Controllers


The character controller (CCT) SDK is an external component built on top of the PhysX SDK, in a manner similar to PhysXExtensions.

CCTs can be implemented in a number of ways: the PhysX implementation in the CCT module is only one of them.

By nature, CCTs are often very game-specific, and they can have a number of unique features in each game. For example the character’s bounding volume may be a capsule in one game, and an inverted pyramid in another. The CCT SDK does not attempt to provide a one-size-fits-all solution that would work out-of-the-box for all possible games. But it provides the basic features common to all CCTs: character control and character interactions. It is a default starting point for users, a strong base that one can build on, and later modify or customize if needed.

Kinematic Character Controller

The PhysX CCT is a kinematic controller. Traditionally, character controllers can be either kinematic or dynamic. A kinematic controller directly works with input displacement vectors (1st order control). A dynamic controller works with input velocities (2nd order control) or forces (3rd order control).

In the past, games did not use a ‘real’ physics engine like the PhysX SDK. But they still used a character controller to move a player in a level. These games, such as Quake or even Doom, had a dedicated, customized piece of code to implement collision detection and response, which was often the only piece of physics in the whole game. It actually had little physics, but a lot of carefully tweaked values to provide a good feeling while controlling the player. The particular behavior it implemented is often called the ‘collide and slide’ algorithm, and it has been ‘tweaked for more than a decade’. The PhysX CCT module is an implementation of such an algorithm, providing a robust and well-known behavior for character control.

The main advantage of kinematic controllers is that they do not suffer from the following issues, which are typical for dynamic controllers:

  • (lack of) continuous collision detection: typical physics engines use discrete collision checks, leading to the notorious ‘tunneling effect’ that has plagued various commercial & non-commercial physics packages for years. This leads to three main problems:

    • the tunneling effect itself : if the character goes too fast it might tunnel through a wall

    • as a consequence, the character’s maximum velocity be limited (thus also limiting the gameplay possibilities)

    • even if it does not tunnel, the character might jitter when pushed forward in a corner for example, because the physics engine keeps moving it back and forth to slightly different positions.

  • No direct control: a rigid body is typically controlled with impulses or forces. It is usually not possible to move it directly to its final position: instead one must convert the delta position vector to impulses/forces, apply them, and hope that the character will end up at the desired position. This does not always work well, in particular when the physics engine uses an imperfect linear solver.

  • Trouble with friction: when the character is standing on a ramp, it should not slide. So infinite friction is needed here. When the character is moving forward on that same ramp, it should not slow down. One does not need any friction here. Similarly, when the character is sliding against a wall, it should not slow down either. Thus, for a CCT, friction is usually either 0 or infinite. Unfortunately the friction model in a physics engine might not be perfect, and it is easy to end up with either a small amount of friction (the character slows down a tiny bit) or a very-large-but-not-infinite friction (the character slides very slowly on that ramp no matter how artificially big the friction parameters are). The conflicting requirements for ramps also mean that usually there is simply no way to perfectly model desired behavior.

  • Trouble with restitution: typically, restitution should be avoided for CCTs. When the character moves fast and collides with a wall, it should not bounce away from it. When the character falls from a height and lands on the ground, flexing his legs, any bounce should be prevented. But once again, even when the restitution is exactly zero, a physics engine can nonetheless make the CCTs bounce a bit. This is not only related to the imperfect nature of the linear solver, it also has to do with how typical penetration-depth-based engines recover from overlap situations, sometimes applying excessive forces that separate the objects too much.

  • Undesired jumps: characters must often stick to the ground, no matter what the physical behavior should be. For example characters in action games tend to move fast, at unrealistic speeds. When they reach the top of a ramp, the physics engine often makes them jump a bit, in the same way a fast car would jump in the streets of San Francisco. But that is often not the desired behavior: instead the character should often stick to the ground regardless of its current velocity. This is sometimes implemented using fixed joints, but this is an unnecessarily complex solution to a problem that is easily prevented with kinematic controllers.

  • Undesired rotations: a typical character is always standing up and never rotating. However physics engines often have poor support for that sort of constraints, and a great deal of effort is often put into preventing a capsule around the character from falling (it should always stands up on its tip). This is often implemented using artificial joints, and the resulting system is neither very robust nor very fast.

To summarize, a lot of effort can be spent on tweaking and disabling the physics engine’s features simply to emulate what is otherwise a much less complex piece of custom code. It is natural to instead keep using that simple piece of custom code.

Creating a character controller

First, create a controller manager somewhere in your application. This object keeps track of all created controllers and allows characters from the same manager to interact with each other. Create the manager using the PxCreateControllerManager() function:

PxScene* scene;    // Previously created scene
PxControllerManager* manager = PxCreateControllerManager(*scene);

Then, create one controller for each character in the game. At the time of writing only boxes (PxBoxController) and capsules (PxCapsuleController) are supported. A capsule controller for example, is created this way:

PxCapsuleControllerDesc desc;
<fill the descriptor here>
PxController* c = manager->createController(desc);

The manager class will keep track of all created controllers. They can be retrieved at any time using the PxControllerManager::getNbControllers() and PxControllerManager::getController() functions. To release a character controller, simply call PxController::release(). To release all created character controllers at once, either release the manager object itself, or use PxControllerManager::purgeControllers() if you intend to keep using the manager.

Note that you can only create a single manager class per PxScene.

Overlap Recovery Module

Ideally, character should not be created in an initial overlap state, i.e. they should be created in a position where they do not overlap the surrounding geometry. The various PxScene overlap functions can be used to check the desired volume of space is empty, prior to creating the character. By default the CCT module does not check for overlaps itself, and creating a character that initially overlaps the world’s static geometry can have undesired and undefined behavior - like the character going through the ground for example.

However, the overlap recovery module can be used to automatically correct the character’s initial position. As long as the amount of overlap is reasonable, the recovery module should be able to relocate the character to a proper, collision-free position.

The overlap recovery module can be useful in several other situations. There are three main cases:

  • when the CCT is directly spawned or teleported in another object

  • when the CCT algorithm fails due to limited FPU accuracy

  • when the “up vector” is modified, making the rotated CCT shape overlap surrounding objects

When activated, the CCT module will automatically try to resolve the penetration, and move the CCT to a safe place where it does not overlap other objects anymore. This only concerns static objects, dynamic objects are ignored by this module.

Enable or disable the overlap recovery module with PxControllerManager::setOverlapRecoveryModule().

By default the character controllers use precise sweep tests, whose accuracy is usually enough to avoid all penetration - provided the contact offset is not too small. Thus, in most cases the overlap recovery module is not needed. When it is used though, the sweep tests can be switched to less accurate but potentially faster versions, using PxControllerManager::setPreciseSweeps().

Character Volume

The character uses a bounding volume that is independent from already existing shapes in the SDK. We currently support two different shapes around the character:

  • An AABB (axis-aligned bounding box), defined by a center position and an extents vector. The AABB does not rotate. It always has a fixed rotation even when the player is (visually) rotating. This avoids getting stuck in places too tight to let the AABB rotate.

  • A capsule, defined by a center position, a vertical height and a radius. The height is the distance between the two sphere centers at the end of the capsule. The capsule has a better behavior when climbing stairs, for example. It is the recommended default choice.


Note: versions prior to 2.3 also supported a sphere. This has been removed since the PxCapsuleController is more robust and provides the same functionality (zero length capsule).

A small skin is maintained around the character’s volume, to avoid numerical issues that would otherwise happen when the character touches other shapes. The size of this skin is user-defined. When rendering the character’s volume for debug purpose, remember to expand the volume by the size of this skin to get accurate debug visualization. This skin is defined by PxControllerDesc::contactOffset and later available through the PxController::getContactOffset() function.

Volume Update

Sometimes it is useful to change the size of the character’s volume at runtime. For example if the character can crouch, it might be required to reduce the height of its bounding volume so that it can then move to places he could not reach otherwise.

For the box controller, the related functions are PxBoxController::setHalfHeight(), PxBoxController::setHalfSideExtent() and PxBoxController::setHalfForwardExtent().

For the capsule controller, use PxCapsuleController::setRadius() and PxCapsuleController::setHeight().

Changing the size of a controller using the above functions does not actually change its position. So if the character is standing on the ground (touching it), and its height is suddenly reduced without updating its position, the character will end up levitating above the ground for a few frames until gravity makes it fall and touch the ground again. This happens because the controllers positions are located at the center of the shapes, rather than the bottom. Thus, to modify a controller’s height and preserve its bottom position, one must change both the height and position of a controller. The PxController::resize() helper function does that automatically.


It is important to note that volumes are directly modified without any extra tests, and thus it might happen that the resulting volume overlaps some geometry nearby. For example when resizing the character to leave a crouch pose, i.e. when the size of the character is increased, it is important to first check that the character can indeed ‘stand up’: the volume of space above the character must be empty (collision free). It is recommended to use the various PxScene overlap queries for this purpose, e.g. PxScene::overlap().

Moving a Character Controller

The heart of the CCT algorithm is the function that actually moves characters around (PxController::move()):

PxControllerCollisionFlags collisionFlags =
    PxController::move(const PxVec3& disp, PxF32 minDist, PxF32 elapsedTime,
    const PxControllerFilters& filters, const PxObstacleContext* obstacles=NULL);

disp is the displacement vector for current frame. It is typically a combination of vertical motion due to gravity and lateral motion when your character is moving. Note that users are responsible for applying gravity to characters here.

minDist is a minimal length used to stop the recursive displacement algorithm early when remaining distance to travel goes below this limit.

elapsedTime is the amount of time that passed since the last call to the move function.

filters are filtering parameters similar to the ones used in the SDK. Use these to control what the character should collide with.

obstacles are optional additional obstacle objects with which the character should collide. Those objects are fully controlled by users and do not need to have counterpart SDK objects. Note that touched obstacles are cached, meaning that the cache needs to be invalidated if the collection of obstacles changes.

collisionFlags is a bit mask returned to users to define collision events that happened during the move. This is a combination of PxControllerCollisionFlag flags. It can be used to trigger various character animations. For example your character might be falling while playing a falling idle animation, and you might start the land animation as soon as PxControllerCollisionFlag::eCOLLISION_DOWN is returned.

It is important to understand the difference between PxController::move() and PxController::setPosition(). The PxController::move function is the core of the CCT module. This is where the aforementioned ‘collide-and-slide’ algorithm takes place. So the function will start from the CCT’s current position, and use sweep tests to attempt to move in the required direction. If obstacles are found, it may make the CCT slide smoothly against them. Or the CCT can get blocked against a wall: the result of the move call depends on the surrounding geometry. On the contrary, PxController::setPosition is a simple ‘teleport’ function that will relocate the CCT to desired position no matter what, regardless of where the CCT starts from, regardless of surrounding geometry, and even if the required position is in the middle of another object.

Graphics Update

Each frame, after PxController::move() calls, graphics object must be kept in sync with the new CCT positions. Controllers’ positions can be accessed using PxController::getPosition(). This function returns the position from the center of the collision shape, since this is what is used internally both within the PhysX SDK and by usual graphics APIs. Note that the position uses double-precision, to make the CCT module work well with large worlds. Also note that a controller never rotates so you can only access its position.

Alternative helper functions PxController::getFootPosition() and PxController::setFootPosition() are provided to work using the character’s bottom position, a.k.a. the foot position. Note that the foot position takes the contact offset into account.


Auto Stepping

Without auto-stepping it is easy for a box-controlled character to get stuck against slight elevations of the ground mesh. In the following picture the small step would stop the character completely. It feels unnatural because in the real world a character would just cross this small obstacle without thinking about it.


This is what auto-stepping enables us to do. Without any intervention from the player (i.e. without them thinking about it) the box correctly steps above the minor obstacle.


However, if the obstacle is too big, i.e. its height is greater than the stepOffset parameter, the controller cannot climb automatically, and the character gets stuck (correctly this time):


‘Climbing’ (over this bigger obstacle, for example) may also be implemented in the future, as an extension of auto-stepping. The step offset is defined by PxControllerDesc::stepOffset and later available through the PxController::getStepOffset() function.

Generally speaking, the step offset should be kept as small as possible.

Climbing Mode

The auto-stepping feature was originally intended for box controllers, which are easily blocked by small obstacles on the ground. Capsule controllers, thanks to their rounded nature, do not necessarily need the feature.

Even with a step offset of 0.0, capsules are able to go over small obstacles since their rounded bottom produces an upward motion after colliding with a small obstacle.

Capsules with a non-zero step-offset can go over obstacles higher than the step offset, because of the combined effect of the auto-stepping feature and their rounded shape. In this case the largest altitude a capsule can climb over is difficult to predict, as it depends on the auto-step value, the capsule’s radius, and even the magnitude of the displacement vector.

This is why there are two different climbing modes for capsules (see PxCapsuleClimbingMode):

  • PxCapsuleClimbingMode::eEASY: in this mode, capsules are not constrained by the step offset value. They can potentially climb over obstacles higher than this value.

  • PxCapsuleClimbingMode::eCONSTRAINED: in this mode, an attempt is made to make sure the capsule can not climb over obstacles higher than the step offset.

Up Vector

In order to implement the auto-stepping feature, the SDK needs to know about the ‘up’ vector. The up vector is defined by PxControllerDesc::upDirection and later available through the PxController::getUpDirection() function.

The up vector does not need to be axis-aligned. It can be arbitrary, modified each frame using the PxController::setUpDirection() function, allowing the character to navigate on spherical worlds.

Modifying the up vector changes the way the CCT library sees character volumes. For example a capsule is defined by a PxCapsuleControllerDesc::height, which is the ‘vertical height’ along the up vector. Thus, changing the up vector effectively rotates the capsule from the point of view of the library. The modification happens immediately, without tests to validate that the character does not overlap nearby geometry. It is then possible for the character to be penetrating some geometry right after the call. Using the overlap recovery module is recommended to solve these issues.


In the above picture the capsule on the left uses a vertical up vector and does not collide with the surrounding geometry. On the right the up vector has been set to 45 degrees, and the capsule now penetrates the wall nearby. For most applications the up vector will be constant, and the same for all characters. These issues will only appear for characters navigating in spherical worlds (e.g. planetoids, etc).

Walkable Parts & Invisible Walls

By default the characters can move everywhere. This may not always be a good thing. In particular, it is often desired to prevent walking on polygons whose slope is steep. The SDK can do this automatically thanks to a user-defined slope limit. All polygons whose slope is higher than the limit slope will be marked as non walk-able, and the SDK will not let characters go there.

Two modes are available to define what happens when touching a non walk-able part. The desired mode is selected with PxControllerDesc::nonWalkableMode:

The slope limit is defined by PxControllerDesc::slopeLimit and later available through the PxController::getSlopeLimit() function. The limit is expressed as the cosine of desired limit angle. For example this uses a slope limit of 45 degrees:

slopeLimit = cosf(PxMath::degToRad(45.0f));

Using slopeLimit = 0.0f automatically disables the feature (i.e. characters can go everywhere).

This feature is not always needed. A common strategy is to disable it and place invisible walls in the level, to restrict player’s movements. The character module can also create those walls for you, if PxControllerDesc::invisibleWallHeight is non-zero. In this case the library creates those extra triangles on the fly, and that parameter controls their height (extruded in the user-defined up direction). A common problem is that those invisible walls are only created when non-walkable triangles are found. It is possible for a jumping character to go over them, if its bounding volume is too small and does not collide with the non-walkable triangles below him. The PxControllerDesc::maxJumpHeight parameter addresses this issue, by extending the size of the bounding volume downward. That way all potentially non-walkable triangles are properly returned by the collision queries, and invisible walls are properly created - preventing the character from jumping on them.

A known limitation is that the slope limit mechanism is currently only enabled against static objects. It is not enabled against dynamic objects, and in particular against kinematic objects. It is also not supported for static spheres or static capsules.

Obstacle Objects

Sometimes it is convenient to create additional obstacles for the CCT to collide with, without creating an actual SDK object. This is useful in a number of situations. For example:

  • the obstacles might only exist for a couple of frames, in which case creating and deleting SDK objects is not always efficient.

  • the obstacles might only exist for stopping the characters, not the SDK’s dynamic objects. This would be for example invisible walls around geometry, that only the characters should collide with. In this case it may not be very efficient to create the invisible walls as SDK objects, since their interactions would then have to be filtered out for everything except the characters. It is potentially more efficient to create those additional invisible walls as external obstacles, that only characters can interact with.

  • the obstacles might be dynamic and updated with a variable timestep, while the SDK uses a fixed timestep. This could be for example a moving platform on which the characters can stand.

At the time of writing the character controller supports box and capsule PxObstacle objects, namely PxBoxObstacle and PxCapsuleObstacle. To create those, first create a PxObstacleContext object using the PxControllerManager::createObstacleContext() function. Then manage obstacles with PxObstacleContext::addObstacle(), PxObstacleContext::removeObstacle() and PxObstacleContext::updateObstacle().

Typically updateObstacle is called right before the controllers’ move calls.

Using obstacles for e.g. moving platforms has the benefit that they can be updated with a variable timestep, just like the character controllers, while the PhysX SDK and regular dynamic rigid bodies are best updated using fixed timesteps.

Hit Callback

The PxUserControllerHitReport object is used to retrieve some information about controller’s evolution. In particular, it is called when a character hits a shape, another character, or a user-defined obstacle object.

When the character hits a shape, the PxUserControllerHitReport::onShapeHit() callback is invoked - for both static and dynamic shapes. Various impact parameters are sent to the callback, and they can then be used to do things like playing sounds, rendering trails, applying forces, and so on. Note that this callback will only be called in response to a character moving against a shape. It will not be called if a (dynamic) shape collides against an otherwise non-moving character. In other words, this will only be called during a PxController::move() call.

When the character hits another character, i.e. another object controlled by a character controller, the PxUserControllerHitReport::onControllerHit() callback is invoked. This happens when the player collides with an NPC, for example.

Finally, when the character hits a user-defined obstacle the PxUserControllerHitReport::onObstacleHit() callback is invoked.

Behavior Callback

The PxControllerBehaviorCallback object is used to customize the character’s behavior after touching a PxShape, a PxController, or a PxObstacle. This is done using the following functions:

At the time of writing the following returned flags are supported:

PxControllerBehaviorFlag::eCCT_CAN_RIDE_ON_OBJECT defines if the character can effectively travel with the object it is standing on. For example a character standing on a dynamic bridge made of rigid bodies connected by joints should follow the motion of the PxShape it is standing on. But it should not be the case if the character stands on, say a PxShape bottle or marbles rolling on the ground. Note that this flag only controls the horizontal displacement communicated from an object to the controller. The vertical motion is something slightly different, as many factors contribute to this displacement: the step offset used to automatically walk over small bumps, the vertical motion of underlying dynamic actors, etc.

PxControllerBehaviorFlag::eCCT_SLIDE defines if the character should slide or not when standing on the object. This can be used as an alternative to the previously discussed slope limit feature, to define non walk-able objects rather than non-walkable parts. It can also be used to make a capsule character fall off a platform’s edge automatically, when the center of the capsule crosses the platform’s edge.

PxControllerBehaviorFlag::eCCT_USER_DEFINED_RIDE simply disables all built-in code related to controllers riding on objects. This can be useful to get the legacy behavior back, which can sometimes be necessary when porting to PhysX 3.x a piece of code built around the PhysX 2.x character controller. The flag simply skips the new codepath, and lets users deal with this particular problem in their own application, outside of the CCT library.

Character Interactions: CCT-vs-dynamic actors

It is tempting to let the physics engine push dynamic objects by applying forces at contact points. However it is often not a very convincing solution.

The bounding volumes around characters are artificial (boxes, capsules, etc) and invisible, so the forces computed by the physics engine between a bounding volume and its surrounding objects will not be realistic anyway. They will not properly model the interaction between an actual character and these objects. If the bounding volume is large compared to the visible character, maybe to make sure that its limbs never penetrate the static geometry around, the dynamic objects will start moving (pushed by a bounding volume) before the actual character touches them - making it look like the character is surrounded by some kind of force field.

Additionally, the pushing effect should not change when switching from a box controller to a capsule controller. It should ideally be independent from the bounding volume.

Pushing effects are usually dictated by gameplay, and sometimes require extra code like inverse kinematic solvers, which are outside of the scope of the CCT module. Even for simple use cases, it is for example difficult to push a dynamic box forward with a capsule controller: since the capsule never hits the box exactly in the middle, applied force tends to rotate the box - even if gameplay dictates that it should move in a straight line.

Thus, this is an area where the CCT module should best be coupled to specific game code, to implement a specific solution for a specific game. This coupling can be done in many different ways. For simple use cases it is enough to use the PxUserControllerHitReport::onShapeHit() callback to apply artificial forces to surrounding dynamic objects.

Note that the character controller does use overlap queries to determine which shapes are nearby. Thus, SDK shapes that should interact with the characters (e.g. the objects that the character should push) must have the PxShapeFlag::eSCENE_QUERY_SHAPE flag set to true, otherwise the CCT will not detect them and characters will move right through these shapes.

Character Interactions: CCT-vs-CCT

The interactions between CCTs (i.e. between two PxController objects) are limited, since in this case both objects are effectively kinematic objects. In other words their motion should be fully controlled by users, and neither the PhysX SDK nor the CCT module should be allowed to move them.

The PxControllerFilterCallback object is used to define basic interactions between characters. Its PxControllerFilterCallback::filter() function can be used to determine if two PxController objects should collide at all with each other. To make CCTs always collide-and-slide against each other, make it return true. To make CCTs always move freely through each other, make it return false.

Otherwise, customized and maybe gameplay-driven filtering rules can be implemented in this callback. Sometimes the filtering changes at runtime, and two characters might be allowed to go through each other only for a limited amount of time. When that limited time expires, the characters may be left in an overlapping state until they separate and move again towards each other. To automatically separate overlapping characters, the PxControllerManager::computeInteractions() function can be used. This function is an optional helper to properly resolve overlaps between characters. It should be called once per frame, before the PxController::move() calls. The function will not move the characters directly, but it will compute overlap information for each character that will be used in the next PxController::move() call.

Hidden Kinematic Actors

The CCT library creates a kinematic actor under the hood, for each controlled character. When invoking the PxController::move() function, the underlying hidden kinematic PxActor is also updated to reflect the CCT position in the physics scene.

Users should be aware of these hidden entities, since the total number of actors in the scene will be higher than the number they created themselves. Additionally they might get back these potentially confusing unknown actors from scene-level collision queries.

One possible strategy is to retrieve the controllers’ kinematic actors using PxController::getActor(), then mark these actors with a special tag, using the PxRigidDynamic::userData field. That way the CCT actors can easily be identified (and possibly ignored) in collision queries or contact reports. Another option to identify these actors could be simply to store them in a hash-set on the users’ side. Actors returned from a scene query could then be compared against that hash-set, to filter out undesired actors created by the character controllers.

Time Stepping

Actors used internally by the CCT library follow the same rules as any other PhysX objects. In particular, they are updated using fixed or variable timesteps. This can be troublesome because the PxController objects are otherwise often updated using variable time steps (typically using the elapsed time between two rendering frames). Thus the PxController objects (using variable time steps) may not always be perfectly in sync with their kinematic actors (using fixed time steps). This phenomenon is normal and usually not a source of trouble.

In a scenario involving for example a 3rd-person view game and a character walking on a dynamic platform implemented as an obstacle object, the order in which systems are updated is also very important to avoid jittering. The obstacle object should be updated first, then the character controller, then the camera linked to the character.

Invalidating Internal Geometry Caches

The CCT library caches the geometry around each character, in order to speed up collision queries. The temporal bounding box for a character is an AABB around the character’s motion (it contains the character’s volume at both its start and end position). The cached volume of space is determined by the size of the character’s temporal bounding box, multiplied by a constant factor. This constant factor is defined for each character by PxControllerDesc::volumeGrowth. Each time a character moves, its temporal bounding box is tested against the cached volume of space. If the motion is fully contained within that volume of space, the contents of the cache are reused instead of regenerated through PxScene-level queries.


In PhysX 3.3 and above, those caches should be automatically invalidated when a cached object gets updated or removed. However it is also possible to manually flush those caches using PxController::invalidateCache().

Prior to deciding if a character will travel with the motion of an object that is touching the character, a number of tests are automatically performed to decide if the cached touched object remains valid. These automatic validity tests mean that in the following cases it is not strictly necessary to invalidate the cache:

  • If the shapes actor is released

  • If the shape is released

  • If the shape is removed from an actor

  • If an actor is removed from scene or moved to another one

  • If the shapes scene query flag changed

  • If the filtering parameters of the shape or the scene have changed.

If a cached touched object is no longer actually touching the character and it is desired that the character no longer travels with the motion of that cached object then it is necessary to invalidate the cache. This holds true if the pair have separated as a consequence of an updated global pose or modified geometry.

Runtime Tessellation

The CCT library is quite robust, but sometimes suffers from FPU accuracy issues when a character collides against large triangles. This can lead to characters not smoothly sliding against those triangles, or even penetrating them. One way to effectively solve these problems is to tessellate the large triangles at runtime, replacing them on-the-fly with a collection of smaller triangles. The library supports a built-in tessellation feature, enabled with this function:

void PxControllerManager::setTessellation(bool flag, float maxEdgeLength);

The first parameter enables or disables the feature. The second parameter defines the maximum allowed edge length for a triangle, before it gets tessellated. Obviously, a smaller edge length leads to more triangles being created at runtime, and the more triangles get generated, the slower it is to collide against them.

It is thus recommended to disable the feature at first, and only enable it if experiencing collision problems. When enabling the feature, it is recommended to use the largest possible maxEdgeLength that does fix encountered problems.


In the screenshot, the large magenta triangle on which the character is standing is replaced with the smaller green triangles by the tessellation module. The internal geometry cache is represented by the blue bounding box. Note that only the green triangles touching this volume of space are kept. Thus, the exact number of triangles produced by the tessellation code depends on both the maxEdgeLength parameter and the PxControllerDesc::volumeGrowth parameter.


This section introduces common solutions to common problems with the CCT library.

Character goes through walls in rare cases

  1. Try increasing PxControllerDesc::contactOffset.

  2. Try enabling runtime tessellation with PxControllerManager::setTessellation(). Start with a small maxEdgeLength first, to see if it solves the problem. Then increase that value as much as possible.

  3. Try enabling overlap recovery module with PxControllerManager::setOverlapRecoveryModule().

Tessellation performance issue

  1. Try fine-tuning the maxEdgeLength parameter. Use the largest possible value that still prevents tunneling issues.

  2. Try reducing PxControllerDesc::volumeGrowth.

The capsule controller manages to climb over obstacles higher than the step offset value

  1. Try using PxCapsuleClimbingMode::eCONSTRAINED.

Character Controller Systems using Scene Queries and Penetration Depth Computation

This section is not about the PhysX CCT library specifically but more about custom CCT systems that are implemented on top of the PhysX Scene Query (SQ) system. To improve robustness, Geometry Queries (GQ) can be added to compute and resolve any penetrations that occur due to object movement that does not account for the presence of the controller, or due to numerical precision issues.

Basic Algorithm:

  1. Call a SQ-Sweep from the current position of the CCT shape to its goal position.

  2. If no initial overlap is detected, move the CCT shape to the position of the first hit, and adjust the trajectory of the CCT by removing the motion relative to the contact normal of the hit.

  3. Repeat Steps 1 and 2 until the goal is reached, or until an SQ-Sweep in Step 1 detects an initial overlap.

  4. If a SQ-Sweep in Step 1 detects an initial overlap, use the GQ Penetration Depth computation function to generate a direction for depenetration. Move the CCT shape out of penetration and begin again with Step 1.

Limitations and Problems

Step 4 of the algorithm above can sometimes run into trouble due to implementation differences in SQ-Sweep, SQ-Overlap and and GQ-Penetration Depth queries. Under certain initial conditions it is possible that the SQ system will determine that a pair of objects is initially overlapping while the GQ -Penetration Depth computation will report them as disjoint (or vice-versa). Penetration depth calculations involving convex hulls operate by shrinking the convex hull and performing distance calculations between a shape and the shrunken convex hull. To understand the conditions under which this occurs and how to resolve the artefacts, please refer to the diagrams and discussion below. Each diagram represents the initial conditions of two shapes, a Character Controller shape (red boxes), a convex obstacle (black boxes), at the time that Step 1 of the algorithm above is executed. In the diagrams, the outermost rectangular black box is the convex hull as seen by the SQ algorithms; the inner black box with a dashed line represents the shrunken convex shape and the black box with rounded corners is the shrunken convex shape inflated by the amount by which we shrunk. These three black boxes are used by the GQ-Penetration Depth computation. Although the example refers to convex hull obstacles, the issue is not exclusive to the convex hull shapes; the problem is similar for other shape types as well.


Diagram 1: CCT Shape Barely Touches an Obstacle

In Diagram 1, the red box of the CCT is barely touching the outermost black box of the convex obstacle. In this situation the SQ-Sweep will report an initial overlap but the GQ-Penetration Depth function will report no hit, because the red box is not touching the black box with rounded corners.

To resolve this, inflate the CCT shape for the GQ-Penetration Depth calculation to ensure that it detects an overlap and returns a valid normal. Note that after inflating the CCT shape, the GQ-Penetration Depth function will report that the shapes are penetrated more deeply than they actually are, so take this additional penetration into account when depenetrating in Step 4. This may result in some clipping around the corners and edges of convex objects but the CCT’s motion should be acceptable. As the corners/edges become more acute, the amount of clipping will increase.


Diagram 2: CCT Overlaps an Obstacle Slightly

Diagram 2 shows a case where the CCT initially overlaps the outer black box seen by the SQ system, but does not overlap the shrunken shape seen by the GQ-Penetration Depth calculator. The GQ-Penetration Depth system will return the penetration from point c to point b but not from point c to point a. Therefore the CCT may clip through the corner of the convex hull after depenetration. This can be corrected in Step 4.


Diagram 3: CCT Overlaps an Obstacle Significantly

As can been seen from Diagram 3, if the CCT penetrates sufficiently that it overlaps with the shrunken shape seen by GQ, the GQ-Penetration Depth calculator will return the penetration from point c to point a. In this case, the GQ-Penetration Depth value can be used without modification in Step 4. However, as this condition would be difficult to categorize without additional computational cost, it is best to inflate the shape as recommended in Step 4 and then subtract this inflation from the returned penetration depth.

Unified MTD Sweep

A recent addition to the scene query sweeps is the flag PxHitFlag::eMTD. This can be used in conjunction with default sweeps to generate the MTD (Minimum Translation Direction) when an initial overlap is detected by a sweep. This flag is guaranteed to generate an appropriate normal under all circumstances, including cases where the sweep may detect an initial overlap but calling a stand-alone MTD function may report no hits. It still may suffer from accuracy issues with penetration depths but, in the cases outlined above around corners/edges, it will report a distance of 0 and the correct contact normal. This can be used to remove components of the sweep moving into the normal direction and then re-sweeping when attempting to implement a CCT. This also generates compound MTDs for meshes/heightfields, which means that it reports an MTD that de-penetrates the shape from the entire mesh rather than just an individual triangle, if such an MTD exists.