Skip to main content

scene_queries_shape_casting

Shape-casting (aka. sweep tests) is the big brother of ray-casting. The only difference with ray-cast is that instead of being a point travelling along a straight line, we have a complete shape travelling along a straight line. This is typically used for character controllers in games to determine by how much the player can move before it hits the environment.

info

Just like ray-casting, it is possible to control the behavior of the shape-casting like limiting the distance travelled by the shape cast, and ignoring some colliders. See the details about the max_toi and query options arguments in the ray-casting section.

The shape-casting along a straight line is performed by r3TryCastShape. This method has similar arguments as r3TryCastRay except that the ray is replaced by three arguments: the shape being cast, the initial position of the shape (this is analog to origin) and the linear velocity the shape is travelling at (this is analog to direction), and the max_toi is replaced by the R3ShapeCastOptions:

R2SharedShape *shape = r2CuboidSharedShape(r2Vector(1.0, 2.0));
R2Pose shape_pos = r2Pose(r2Vector(0.0, 1.0), r2Rotation(0.2));
R2Vector shape_vel = r2Vector(0.1, 0.4);
R2QueryOptions options = r2DefaultQueryOptions();
R2ShapeCastOptions cast_options = r2DefaultShapeCastOptions();
cast_options.max_time_of_impact = 4.0;
cast_options.target_distance = 0.0;
cast_options.stop_at_penetration = 0;
cast_options.compute_impact_geometry_on_penetration = 0;

R2OptionalShapeCastHit result = r2TryCastShape(world, &options, shape_pos, shape_vel, shape, cast_options);
if (result.found) {
R2ShapeCastHit hit = result.hit;
// The first collider hit has the handle `hit.collider`. The `hit` is a
// structure containing details about the hit configuration.
printf("Hit the collider %u with the time of impact %f\n", hit.collider.index,
(double)hit.time_of_impact);
}

// The shape is owned by the application.
r2FreeSharedShape(shape);

The R3ShapeCastOptions, initialized by r3DefaultShapeCastOptions, control the behavior of the shape-casting:

  • max_time_of_impact plays the role of the max_toi of the ray-casts: the shape travels at most shape_vel * max_time_of_impact.
  • target_distance makes the shape-casting report a hit as soon as the cast shape gets closer than this distance to a collider, instead of waiting for an actual contact.
  • stop_at_penetration controls the behavior of the shape-casting if the shape is already intersecting a collider at its initial position. If it is 1, that collider is reported with a time-of-impact equal to zero. If it is 0, that penetration is ignored if the motion is separating the shapes, and the shape-casting searches for a later impact.
  • compute_impact_geometry_on_penetration is detailed below.

r3TryCastShape sets the found field of its result to 0 if the shape doesn't hit anything, whereas r3CastShape reports this as the R3_NOT_FOUND error.

The result of the shape-casting includes the handle of the first collider being hit (hit.collider), as well as detailed information about the geometry of the hit:

  • hit.time_of_impact: indicates the time of impact between the shape and the collider hit. This means that after travelling a distance of shape_vel * hit.time_of_impact the collider and the cast shape are exactly touching. If hit.time_of_impact == 0.0 then the shape is already intersecting a collider at its initial position.
  • hit.witness1: indicates the contact point on the collider hit when the cast shape and the collider are touching, expressed in world-space.
  • hit.witness2: indicates the contact point on the cast shape when the cast shape and the collider are touching, expressed in the local-space of the cast shape.
  • hit.normal1: indicates the outward normal of the collider hit at the contact point hit.witness1, expressed in world-space.
  • hit.normal2: indicates the outward normal of the cast shape at the contact point hit.witness2, expressed in the local-space of the cast shape.

Because the cast shape moved, hit.witness2 and hit.normal2 can be converted to world-space by applying the pose of the cast shape at the time of impact, i.e., its initial pose translated by shape_vel * hit.time_of_impact.

If the shape was already intersecting a collider at its initial position (hit.status is then R3_SHAPE_CAST_PENETRATING), the witness points and normals are only reliable if the compute_impact_geometry_on_penetration field of the R3ShapeCastOptions is set to 1.

Nonlinear shape-casting​

The shape-casting above only moves the shape along a straight line: its orientation doesn't change during the cast. If the rotation of the shape matters, r3TryCastShapeNonlinear performs a nonlinear shape-casting: the shape follows a rigid motion combining a constant linear velocity and a constant angular velocity. This motion is described by an R3NonlinearRigidMotion which contains the initial pose of the shape (start), its linear and angular velocities (linvel and angvel), and the local-space point around which the shape rotates (local_center). At time tt, the shape is rotated by the angular velocity times tt around that point, and translated by the linear velocity times tt. The first impact is searched for between the start_time and end_time arguments (start_time must not be greater than end_time). This is typically useful to predict if a rotating object (e.g. a spinning blade, a swinging door, or the collider of a rigid-body with a non-zero angular velocity) will hit something during a timestep.

If the shape is already intersecting a collider at start_time, setting stop_at_penetration to 1 makes the cast report that collider with a time of impact equal to start_time. If it is 0, that penetration is ignored when the motion is separating the shapes, and the cast searches for a later impact that would result in tunnelling. The result has the same form as for r3TryCastShape (with hit.witness1 and hit.normal1 in world-space, and hit.witness2 and hit.normal2 in the local-space of the cast shape, whose pose at the time of impact is given by r3NonlinearRigidMotion_PositionAtTime). Nonlinear shape-casting is more expensive than the linear one, so it is recommended to use r3TryCastShape whenever the shape doesn't rotate.