Continuous collision detection§
Continuous collision detection (CCD) is currently only implemented for colliders (including sensors) attached to
RigidBody or a
Ground body. Enabling CCD on colliders attached to deformable bodies and multibodies will not
have any effect.
Continuous collision detection (CCD) is a technique that aims at avoiding the so-called tunneling effect. The tunneling effect is when some contacts are simply missed by the physics engine because of the discrete nature of collision detection, causing visual and logical artifacts.
If your player is moving or falling pretty fast and just passes through the floor or a wall as if it weren’t there, then you have been hit by the tunneling effect! If your player shoots a fast moving bullet modeled as a collider attached to a rigid body, but this bullet appears to just ignore its target, then you are subject to the tunneling effect!
The tunneling effect§
This effect is a known limitation of most real-time physics engine and is due to the fact they detect collisions in a
discrete manner. Indeed, every time you do a
mechanical_world.step(...), the physics engine will advance the position
of every body by an amount proportional to their velocity and performs collision detection on their colliders at their new position.
The geometric world has only a limited visibility on what happen between the initial and final positions of the collider,
so it may not see contacts that should have happened between those positions.
In the following example, the gray rectangle is moving while the yellow rectangle is a static wall. The initial position of the moving rectangle is on the left, at the time . This rectangle is moving so fast that its final position seen by the mechanical and geometrical world is given on the right, at the time . Because of the discrete nature of position integration and collision detection, the physics engine did not see that there was a wall between those two positions because it did not consider the whole trajectory (shown with dotted lines) of the moving rectangle for collision detection:
CCD prevents this issue by performing more computations to make the geometric world aware of the full trajectory of the moving object. That way, the first time the moving rectangle touches the wall can be detected. This time is also called their first time of impact (TOI). Detecting this will allow the mechanical world to teleport the moving rectangle back to its first impact, and apply forces. This will yield the expected behavior:
But taking the exact motion of the moving rectangle can be quite computationally intensive, so that’s why most physics engines only assume a linear motion when performing CCD. This implies a similar result as the previous picture, except that the orientation of the moving rectangle will not be corrected and will remain the same as if CCD is were enabled:
CCD that takes rotations into account is more realistic but less computationally efficient. It is called nonlinear CCD. On the other hand CCD that does not take rotations into account is less realistic but more efficient. It is called linear CCD.
What about sensors?§
Sensors can suffer from the same tunneling effect as regular colliders. Let’s take the same example as the previous section, except
that the yellow wall is a sensor. Also assume that our application is setup such that a counter is incremented every time
Proximity::Intersecting event involving this sensor is generated. If CCD is not enabled for our sensor nor our moving
rectangle, no proximity event will be generated by the geometric world because it could not see the whole trajectory of
the moving rectangle. If, however CCD is enabled, then a
Proximity::Intersecting event followed by a
will be generated, allowing the counter to be incremented.
CCD in nphysics§
Currently, nphysics supports CCD on colliders (including sensors) attached to a
RigidBody or a
Ground body. Enabling CCD on colliders
attached to a multibody or a deformable body won’t have any effect. CCD is disabled by default and can be enabled when
building a collider:
let collider = ColliderDesc::new(shape)
let collider_handle = collider_set.insert(collider);
It can also be enabled or disabled on an existing collider:
let collider = collider_set.get(collider_handle);
It is allowed to:
- Enable CCD on a sensor.
- Have multiple colliders with CCD enabled attached to a single body.
- Have multiple colliders attached to a single body, but only some of them have CCD enabled.
It may seem surprising that CCD is enabled on the colliders instead of the rigid bodies like most other physics engines do. This is a deliberate choice we made as we think it makes more sense. From our point of view, enabling CCD for a collider basically means that nothing can pass through this collider without the geometric world being aware of it. Thus, if you enable CCD on a static wall, this wall will not be traversable by any collider attached to moving bodies, even if those colliders don’t have CCD enabled themselves.
Tuning CCD accuracy and performance§
The default configuration of CCD should work well for typical uses. Though you may need to tune some parameters depending on whether you favor accuracy or performance. In any case, the approach for CCD resolution in nphysics, like most physics engine, is a best-effort approach. This means that it will do all it can to prevent tunneling, but some tunneling may still happen in some edge cases, for example if the number of iterations are too limited to resolve some complex penetration configurations.
CCD with multiple substeps§
In order to avoid tunneling, the CCD solver will perform what is called substeps during a single call to
Substeps are smaller timesteps known to be collision-free. In the previous examples, the mechanical world performed
only one substep (which is the default) which allowed the grey rectangle with CCD enabled to travel just enough time to end up in a configuration
where it touches the wall.
Now consider the following example involving a fast moving ball. If CCD is not enabled, it is fast enough to miss the collision with the yellow floor (left image). If CCD is enabled with one substep, then it will stop at its first time of impact (middle image). By allowing the mechanical world to perform two substeps, the ball will continue its trajectory after this first contact is resolved, causing it it bounce and touch the vertical wall (right image).
The number of allowed substeps will control the number of allowed bounces handled in a single call of
Keeping the number of substeps to 1 is generally sufficient to get visually plausible results for most application so it
is the default. Though if your application does require more substeps, it can be configured on the mechanical world’s
.max_ccd_substeps = 2;
If the maximum number of substeps is set to 0, then CCD will be considered as being completely disabled for every body.
The number of substeps has a significant impact on performances. The higher this number, the more costly CCD will be.
What about sensors?
If a sensor would be traversed multiple times by the same object during multiple substeps of a single timestep, the mechanical world
will generate only one
Proximity::Intersecting event and, if the final position of the collider is disjoint from
the sensor, one
Proximity::Disjoint event . If you actually need as many
pair of events as the number of times the object traverse a single sensor, the flag
multiple_ccd_substep_sensor_events_enabled must be set to
true. Note however that
setting this flag to
true is still experimental so let us know if you are not getting the result you’d expect:
.multiple_ccd_substep_sensor_events_enabled = true;
Linear CCD vs non-linear CCD§
The choice between linear CCD and non-linear CCD depend on the way the motion of a body is interpolated. For rigid bodies, the interpolation mode is set to non-linear by default, which will result in non-linear CCD being used for its CCD-enabled colliders. Linear motion interpolation (which will result in linear CCD being used for their colliders) can be enabled for a specific rigid-body when it is constructed:
let rb = RigidBodyDesc::new()
let rb_handle = body_set.insert(rb);
It can also be enabled or disabled after its addition to the body set:
let rb = body_set.rigid_body_mut(rb_handle);
Penetration handling during CCD resolution§
Penetrations are extremely difficult to handle during CCD resolution as they can cause some colliders with CCD enabled to remain stuck in a penetration state with another collider. To overcome this problem, the CCD solver will:
- Perform a greater number of iterations for the correction of penetrations. This is controlled by the
max_ccd_position_iterationsparameters. Increasing this reduces the likelihood of two CCD-enabled colliders being stuck but increases the cost of CCD resolution:
.max_ccd_position_iterations = 20;
- Ignore CCD between colliders that begin the timestep in a penetration configuration. This is controlled by
ccd_on_penetration_enabledflag and is
falseby default. Setting this to
falsereduces significantly the problem of CCD-enabled colliders getting stuck, but increases the risk of tunneling if the solver does not have enough iterations to solve penterations. Setting this to
trueprevent tunneling in a much more robust way but may cause the simulation to stutter because of colliders that get stuck in a penetration state.
Interaction handling and sensors Performance tuning