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.
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 filter argumentsmax_toi and query options arguments
The shape-casting along a straight line is performed by QueryPipeline::cast_shapeRapierContext::cast_shapeWorld.castShaper3TryCastShapeQueryPipeline.cast_shapeQueryPipeline::cast_rayRapierContext::cast_rayWorld.castRayr3TryCastRayQueryPipeline.cast_rayray.originoriginray.dirdirectionmax_toi is replaced by the R3ShapeCastOptionsmax_toi is replaced by a ShapeCastOptions
- Example 2D
- Example 3D
let shape = Cuboid::new(Vector::new(1.0, 2.0));
let shape_pos = Pose::new(Vector::new(0.0, 1.0), 0.2);
let shape_vel = Vector::new(0.1, 0.4);
let max_toi = 4.0;
let filter = QueryFilter::default();
let options = ShapeCastOptions {
max_time_of_impact: 4.0,
target_distance: 0.0,
stop_at_penetration: false,
compute_impact_geometry_on_penetration: false,
};
let query_pipeline = world.query_pipeline_with_filter(filter);
if let Some((handle, hit)) = query_pipeline.cast_shape(
&shape_pos, shape_vel, &shape, options
) {
// The first collider hit has the handle `handle`. The `hit` is a
// structure containing details about the hit configuration.
println!("Hit the collider {:?} with the configuration: {:?}", handle, hit);
}
let shape = Cuboid::new(Vector::new(1.0, 2.0, 3.0));
let shape_pos = Pose::new(Vector::new(0.0, 1.0, 0.0), Vector::new(0.2, 0.7, 0.1));
let shape_vel = Vector::new(0.1, 0.4, 0.2);
let max_toi = 4.0;
let filter = QueryFilter::default();
let options = ShapeCastOptions {
max_time_of_impact: 4.0,
target_distance: 0.0,
stop_at_penetration: false,
compute_impact_geometry_on_penetration: false,
};
let query_pipeline = world.query_pipeline_with_filter(filter);
if let Some((handle, hit)) = query_pipeline.cast_shape(
&shape_pos, shape_vel, &shape, options
) {
// The first collider hit has the handle `handle`. The `hit` is a
// structure containing details about the hit configuration.
println!("Hit the collider {:?} with the configuration: {:?}", handle, hit);
}
- Example 2D
- Example 3D
/* Cast a shape inside of a system. */
fn cast_shape(rapier_context: ReadRapierContext) {
let rapier_context = rapier_context.single().unwrap();
let shape = Collider::cuboid(1.0, 2.0);
let shape_pos = Vec2::new(1.0, 2.0);
let shape_rot = 0.8;
let shape_vel = Vec2::new(0.1, 0.4);
let filter = QueryFilter::default();
let options = ShapeCastOptions {
max_time_of_impact: 4.0,
target_distance: 0.0,
stop_at_penetration: false,
compute_impact_geometry_on_penetration: false,
};
if let Some((entity, hit)) =
rapier_context.cast_shape(shape_pos, shape_rot, shape_vel, &shape, options, filter)
{
// The first collider hit has the entity `entity`. The `hit` is a
// structure containing details about the hit configuration.
println!(
"Hit the entity {:?} with the configuration: {:?}",
entity, hit
);
}
}
/* Cast a shape inside of a system. */
fn cast_shape(rapier_context: ReadRapierContext) {
let rapier_context = rapier_context.single().unwrap();
let shape = Collider::cuboid(1.0, 2.0, 3.0);
let shape_pos = Vec3::new(1.0, 2.0, 3.0);
let shape_rot = Quat::from_rotation_z(0.8);
let shape_vel = Vec3::new(0.1, 0.4, 0.2);
let filter = QueryFilter::default();
let options = ShapeCastOptions {
max_time_of_impact: 4.0,
target_distance: 0.0,
stop_at_penetration: false,
compute_impact_geometry_on_penetration: false,
};
if let Some((entity, hit)) =
rapier_context.cast_shape(shape_pos, shape_rot, shape_vel, &shape, options, filter)
{
// The first collider hit has the entity `entity`. The `hit` is a
// structure containing details about the hit configuration.
println!(
"Hit the entity {:?} with the configuration: {:?}",
entity, hit
);
}
}
- Example 2D
- Example 3D
let shapePos = { x: 0.0, y: 1.0 };
let shapeRot = 0.2;
let shapeVel = { x: 0.1, y: 0.4 };
let shape = new RAPIER.Cuboid(1.0, 2.0);
let targetDistance = 0.0;
let maxToi = 4.0;
// Optional parameters:
let stopAtPenetration = true;
let filterFlags = QueryFilterFlags.EXCLUDE_DYNAMIC;
let filterGroups = 0x000b0001;
let filterExcludeCollider = collider;
let filterExcludeRigidBody = rigidBody;
let hit = world.castShape(shapePos, shapeRot, shapeVel, shape, targetDistance, maxToi,
stopAtPenetration, filterFlags, filterGroups, filterExcludeCollider, filterExcludeRigidBody);
if (hit != null) {
// The first collider hit has the handle `handle`. The `hit` is a
// structure containing details about the hit configuration.
console.log("Hit the collider", hit.collider, "at time", hit.time_of_impact);
}
let shapePos = { x: 0.0, y: 1.0, z: 0.0 };
let shapeRot = { w: 1.0, x: 0.0, y: 0.0, z: 0.0 };
let shapeVel = { x: 0.1, y: 0.4, z: 1.0 };
let shape = new RAPIER.Cuboid(1.0, 2.0, 3.0);
let targetDistance = 0.0;
let maxToi = 4.0;
// Optional parameters:
let stopAtPenetration = true;
let filterFlags = QueryFilterFlags.EXCLUDE_DYNAMIC;
let filterGroups = 0x000b0001;
let filterExcludeCollider = collider;
let filterExcludeRigidBody = rigidBody;
let hit = world.castShape(shapePos, shapeRot, shapeVel, shape, targetDistance, maxToi,
stopAtPenetration, filterFlags, filterGroups, filterExcludeCollider, filterExcludeRigidBody);
if (hit != null) {
// The first collider hit has the handle `handle`. The `hit` is a
// structure containing details about the hit configuration.
console.log("Hit the collider", hit.collider, "at time", hit.time_of_impact);
}
- Example 2D
- Example 3D
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);
R3SharedShape *shape = r3CuboidSharedShape(r3Vector(1.0, 2.0, 3.0));
// The rotation is given as a scaled axis, i.e., an axis multiplied by the angle.
R3Vector scaled_axis = r3Vector(0.2, 0.7, 0.1);
R3Pose shape_pos = r3Pose(r3Vector(0.0, 1.0, 0.0),
r3RotationFromAxisAngle(scaled_axis, r3VectorLength(scaled_axis)));
R3Vector shape_vel = r3Vector(0.1, 0.4, 0.2);
R3QueryOptions options = r3DefaultQueryOptions();
R3ShapeCastOptions cast_options = r3DefaultShapeCastOptions();
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;
R3OptionalShapeCastHit result = r3TryCastShape(world, &options, shape_pos, shape_vel, shape, cast_options);
if (result.found) {
R3ShapeCastHit 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.
r3FreeSharedShape(shape);
The R3ShapeCastOptions, initialized by r3DefaultShapeCastOptions, control the behavior of the shape-casting:
max_time_of_impactplays the role of themax_toiof the ray-casts: the shape travels at mostshape_vel * max_time_of_impact.target_distancemakes 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_penetrationcontrols the behavior of the shape-casting if the shape is already intersecting a collider at its initial position. If it is1, that collider is reported with a time-of-impact equal to zero. If it is0, that penetration is ignored if the motion is separating the shapes, and the shape-casting searches for a later impact.compute_impact_geometry_on_penetrationis 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.
shape = rp.SharedShape.cuboid(1.0, 2.0, 3.0)
shape_pos = rp.Isometry3(translation=(0.0, 1.0, 0.0), rotation=rp.rotation_from_angle((0.2, 0.7, 0.1)))
shape_vel = (0.1, 0.4, 0.2)
query_filter = rp.QueryFilter()
options = rp.ShapeCastOptions(
max_time_of_impact=4.0,
target_distance=0.0,
stop_at_penetration=False,
compute_impact_geometry_on_penetration=False,
)
query_pipeline = world.query_pipeline
hit = query_pipeline.cast_shape(shape_pos, shape_vel, shape, options, filter=query_filter)
if hit is not None:
handle, hit = hit
# The first collider hit has the handle `handle`. The `hit` is a
# structure containing details about the hit configuration.
print(f"Hit the collider {handle} with the configuration: {hit}")
The cast shape is a SharedShape, and its initial position is an Isometry3. The ShapeCastOptions, whose
constructor takes each of its properties as a keyword argument, control the behavior of the shape-casting:
max_time_of_impactplays the role of themax_toiof the ray-casts: the shape travels at mostshape_vel * max_time_of_impact. It is unbounded by default, andShapeCastOptions.with_max_time_of_impactgives the default options with a finitemax_time_of_impact.target_distancemakes 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_penetrationcontrols the behavior of the shape-casting if the shape is already intersecting a collider at its initial position. If it isTrue(the default), that collider is reported with a time-of-impact equal to zero. If it isFalse, that penetration is ignored if the motion is separating the shapes, and the shape-casting searches for a later impact.compute_impact_geometry_on_penetrationis detailed below.
QueryPipeline.cast_shape returns None if the shape doesn't hit anything, and the handle of the collider hit
together with a ShapeCastHit otherwise.
The result of the shape-casting includes hit.collider)hit.collider)
hit.time_of_impact: indicates the time of impact between the shape and the collider hit. This means that after travelling a distance ofshape_vel * hit.time_of_impactshapeVel * hit.time_of_impactshape_vel * hit.time_of_impact the collider and the cast shape are exactly touching. Ifshape_vel * hit.time_of_impacthit.time_of_impact == 0.0then 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 pointhit.witness1, expressed in world-space.hit.normal2: indicates the outward normal of the cast shape at the contact pointhit.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_impactshapeVel * hit.time_of_impactshape_vel * hit.time_of_impactshape_vel * hit.time_of_impact
If the shape was already intersecting a collider at its initial position, the witness points and normals are only
reliable if ShapeCastOptions::compute_impact_geometry_on_penetration is set to true.
The witness points and normals are grouped into hit.details (a ShapeCastHitDetails). These details are None if
the shape was already intersecting a collider at its initial position (hit.status is then
ShapeCastStatus::PenetratingOrWithinTargetDist) unless ShapeCastOptions::compute_impact_geometry_on_penetration is
set to true. Finally, hit.subshape1 is the index of the part of the collider that was hit if its shape is composed
of several pieces (compound shapes, triangle meshes, etc.)
Note that the frames are different for Collider::cast_shape and Collider::cast_shape_nonlinear, which cast a
collider against another one outside of any physics context: there, every witness point and normal is expressed in the
local-space of its own shape.
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.
If the shape was already intersecting a collider at its initial position (hit.status is then
ShapeCastStatus.PENETRATING_OR_WITHIN_TARGET_DIST), the witness points and normals are only reliable if
ShapeCastOptions.compute_impact_geometry_on_penetration is True (which is its default value).
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, QueryPipeline::cast_shape_nonlinearRapierContext::cast_shape_nonlinearNonlinearRigidMotionNonlinearMotionstart_time and end_time arguments. 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 true makes the cast
report that collider with a time of impact equal to start_time. If it is false, 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 cast_shapehit.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
NonlinearRigidMotion::position_at_time)None if the hit reported is a penetration at
start_time)cast_shape whenever the shape doesn't rotate.
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
, the shape is rotated by the angular velocity times around that point, and translated by the linear velocity
times . 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.
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, QueryPipeline.cast_shape_nonlinear 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 a NonlinearRigidMotion 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
, the shape is rotated by the angular velocity times around that point, and translated by the linear velocity
times . The first impact is searched for between the start_time and end_time arguments. 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:
# The shape rotates around its center (in its local-space) while it translates.
motion = rp.NonlinearRigidMotion(
start=rp.Isometry3(translation=(5.0, 8.0, 0.0)),
local_center=(0.0, 0.0, 0.0),
linvel=(0.0, -4.0, 0.0),
angvel=(0.0, 0.0, 3.0),
)
# Only `stop_at_penetration` is taken into account by the nonlinear shape-casting.
options = rp.ShapeCastOptions(stop_at_penetration=True)
start_time = 0.0
end_time = 2.0
hit = query_pipeline.cast_shape_nonlinear(motion, shape, options, start_time, end_time, filter=query_filter)
if hit is not None:
handle, hit = hit
# The pose of the cast shape at the time of impact gives the world-space
# coordinates of its witness point.
shape_pos_at_impact = motion.position_at_time(hit.time_of_impact)
witness2 = shape_pos_at_impact.transform_point(hit.witness2)
print(f"Hit the collider {handle} at time {hit.time_of_impact}, at point {witness2}")
The only property of the ShapeCastOptions taken into account here is stop_at_penetration: if the shape is already
intersecting a collider at start_time, setting it to True makes the cast report that collider with a time of impact
equal to start_time. If it is False, 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 cast_shape
(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 NonlinearRigidMotion.position_at_time). Nonlinear
shape-casting is more expensive than the linear one, so it is recommended to use cast_shape whenever the shape
doesn't rotate.