Scene queries
Scene queries are geometric queries that take all the colliders of the physics world into account. These queries are available through the QueryPipeline.
Keep in mind that scene queries will only take into account the colliders (and their positions) known during the
last call to QueryPipeline::update
:
// Update the query pipeline to take the latest collider positions into account.
query_pipeline.update(&rigid_body_set, &collider_set);
// Scene queries can now be executed accurately.
If you need to avoid an expensive full rebuild, it is possible to update only specific changes:
// Update the query pipeline with given modifications.
// It is recommended to set the last parameter `refit_and_rebalance`
// to `true` to update the space partitioning for best query performance.
query_pipeline.update_incremental(&collider_set, &modified_colliders, &removed_colliders, true);
// Scene queries can now be executed accurately.
Ray-casting
Ray-casting is a geometric query that finds one or several colliders intersecting a half-line. Ray-casting is an extremely common operation that covers a wide variety of use-cases: firing bullets, character controllers, rendering (for ray-tracing), etc.
A ray is defined by its origin and its direction: it can be interpreted as a single point moving in a straight line towards the ray direction.
In addition to the ray geometric information, ray-casting method allow additional control over the behavior of the ray cast like limiting the length of the ray and ignoring some colliders. See the detailed ray-cast arguments description after the next example.
There are multiple ray-casting methods yielding more or less detailed results (see example bellow). The more results you get, the more computationally expensive the ray-cast will be.
- Example 2D
- Example 3D
let ray = Ray::new(point![1.0, 2.0], vector![0.0, 1.0]);
let max_toi = 4.0;
let solid = true;
let filter = QueryFilter::default();
if let Some((handle, toi)) =
query_pipeline.cast_ray(rigid_body_set, collider_set, &ray, max_toi, solid, filter)
{
// The first collider hit has the handle `handle` and it hit after
// the ray travelled a distance equal to `ray.dir * toi`.
let hit_point = ray.point_at(toi); // Same as: `ray.origin + ray.dir * toi`
println!("Collider {:?} hit at point {}", handle, hit_point);
}
if let Some((handle, intersection)) = query_pipeline.cast_ray_and_get_normal(
rigid_body_set, collider_set, &ray, max_toi, solid, filter,
) {
// This is similar to `QueryPipeline::cast_ray` illustrated above except
// that it also returns the normal of the collider shape at the hit point.
let hit_point = ray.point_at(intersection.time_of_impact);
let hit_normal = intersection.normal;
println!(
"Collider {:?} hit at point {} with normal {}",
handle, hit_point, hit_normal
);
}
query_pipeline.intersections_with_ray(rigid_body_set, collider_set, &ray, max_toi, solid, filter,
|handle, intersection| {
// Callback called on each collider hit by the ray.
let hit_point = ray.point_at(intersection.time_of_impact);
let hit_normal = intersection.normal;
println!(
"Collider {:?} hit at point {} with normal {}",
handle, hit_point, hit_normal
);
true // Return `false` instead if we want to stop searching for other hits.
},
);
let ray = Ray::new(point![1.0, 2.0, 3.0], vector![0.0, 1.0, 0.0]);
let max_toi = 4.0;
let solid = true;
let filter = QueryFilter::default();
if let Some((handle, toi)) = query_pipeline.cast_ray(
rigid_body_set, &collider_set, &ray, max_toi, solid, filter
) {
// The first collider hit has the handle `handle` and it hit after
// the ray travelled a distance equal to `ray.dir * toi`.
let hit_point = ray.point_at(toi); // Same as: `ray.origin + ray.dir * toi`
println!("Collider {:?} hit at point {}", handle, hit_point);
}
if let Some((handle, intersection)) = query_pipeline.cast_ray_and_get_normal(
rigid_body_set, &collider_set, &ray, max_toi, solid, filter
) {
// This is similar to `QueryPipeline::cast_ray` illustrated above except
// that it also returns the normal of the collider shape at the hit point.
let hit_point = ray.point_at(intersection.time_of_impact);
let hit_normal = intersection.normal;
println!("Collider {:?} hit at point {} with normal {}", handle, hit_point, hit_normal);
}
query_pipeline.intersections_with_ray(
rigid_body_set, &collider_set, &ray, max_toi, solid, filter,
|handle, intersection| {
// Callback called on each collider hit by the ray.
let hit_point = ray.point_at(intersection.time_of_impact);
let hit_normal = intersection.normal;
println!("Collider {:?} hit at point {} with normal {}", handle, hit_point, hit_normal);
true // Return `false` instead if we want to stop searching for other hits.
});
Aside from the ray being cast, all these ray-casting methods take a few extra parameters for controlling the behavior of the ray-cast:
max_toi
: is the maximum "time-of-impact" that can be reported by the ray-cast. The notion of "time-of-impact" refer to the fact that a ray can be seen as a point starting atray.origin
moving at a linear velocity equal toray.dir
. Therefore,max_toi
limits the ray-cast to the segment:[ray.origin, ray.origin + ray.dir * max_toi]
.solid
: this argument controls the behavior of the ray-cast ifray.origin
is inside of a shape: ifsolid
istrue
then the hit point will be the ray origin itself (toi = 0.0
) because the interior of the shape will be assumed to be filled with material. Ifsolid
isfalse
then the shape will be assumed to have an empty interior and the hit point will be the first time the ray hits the shape's boundary. The following 2D example illustrates the difference between the two scenarios. The ray is in green and the resulting hit point circled in red:
In addition, it is possible to only apply the scene query to a subsets of the colliders using a query filter.
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
arguments in the ray-casting section.
There is only one shape-casting method: QueryPipeline::cast_shape
.
This method has similar arguments as QueryPipeline::cast_ray
except
that the ray is replaced by three arguments: the shape being cast, the initial position of the shape (this is analog to ray.origin
) and
the linear velocity the shape is travelling at (this is analog to ray.dir
):
- Example 2D
- Example 3D
let shape = Cuboid::new(vector![1.0, 2.0]);
let shape_pos = Isometry::new(vector![0.0, 1.0], 0.8);
let shape_vel = vector![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((handle, hit)) = query_pipeline.cast_shape(rigid_body_set,
collider_set, &shape_pos, &shape_vel, &shape, options, filter
) {
// 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![1.0, 2.0, 3.0]);
let shape_pos = Isometry::new(vector![0.0, 1.0, 0.0], vector![0.2, 0.7, 0.1]);
let shape_vel = vector![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,
};
if let Some((handle, hit)) = query_pipeline.cast_shape(
rigid_body_set, &collider_set, &shape_pos, &shape_vel, &shape, options, filter
) {
// 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);
}
The result of the shape-casting includes the handle of the first collider being hit, as well as detailed information about the geometry of the hit:
hit.toi
: indicates the time of impact between the shape and the collider hit. This means that after travelling a distance ofshape_vel * hit.toi
the collider and the cast shape are exactly touching. Ifhit.toi == 0.0
then the shape is already intersecting a collider at its initial position.hit.witness1
: indicates the contact point when the cast shape and the collider are touching, expressed in the local-space of the collider hit by the shape.hit.witness2
: indicates the contact point when the cast shape and the collider are touching, expressed in the local-space of the cast shape.hit.normal1
: indicates the normal at the contact pointhit.witness1
, expressed in the local-space of the collider hit by the shape.hit.normal2
: indicates the normal at the contact pointhit.witness2
, expressed in the local-space of the cast shape.
Point projection
Point projection will either project a point on the closest collider of the scene (QueryPipeline::project_point
),
or will enumerate every collider containing given point (QueryPipeline::intersections_with_point
).
- Example 2D
- Example 3D
let point = point![1.0, 2.0];
let solid = true;
let filter = QueryFilter::default();
if let Some((handle, projection)) = query_pipeline.project_point(
rigid_body_set,
collider_set, &point, solid, filter
) {
// The collider closest to the point has this `handle`.
println!("Projected point on collider {:?}. Point projection: {}", handle, projection.point);
println!("Point was inside of the collider shape: {}", projection.is_inside);
}
query_pipeline.intersections_with_point(
rigid_body_set, collider_set, &point, filter, |handle| {
// Callback called on each collider with a shape containing the point.
println!("The collider {:?} contains the point.", handle);
// Return `false` instead if we want to stop searching for other colliders containing this point.
true
}
);
let point = point![1.0, 2.0, 3.0];
let solid = true;
let filter = QueryFilter::default();
if let Some((handle, projection)) = query_pipeline.project_point(
rigid_body_set, &collider_set, &point, solid, filter
) {
// The collider closest to the point has this `handle`.
println!("Projected point on collider {:?}. Point projection: {}", handle, projection.point);
println!("Point was inside of the collider shape: {}", projection.is_inside);
}
query_pipeline.intersections_with_point(
rigid_body_set, &collider_set, &point, filter, |handle| {
// Callback called on each collider with a shape containing the point.
println!("The collider {:?} contains the point.", handle);
// Return `false` instead if we want to stop searching for other colliders containing this point.
true
}
);
It is possible to only apply the scene query to a subsets of the colliders using a query filter
Intersection test
Intersection tests will find all the colliders with a shape intersecting a given shape. This can be useful for, e.g., selecting all the objects that intersect a given area. There are two kind of intersection tests:
- The exact intersection test
QueryPipeline::intersections_with_shape
searches for all the colliders with shapes intersecting the given shape. - The approximate intersection test
QueryPipeline::colliders_with_aabb_intersecting_aabb
searches for all the colliders with an AABB intersecting the given AABB. This does not check if the actual shapes of these colliders intersect the AABB.
See the ray-casting section for details about intersection tests between a ray and the colliders on the scene. And see the point projection section for details about the intersection test between the colliders and a point.
- Example 2D
- Example 3D
let shape = Cuboid::new(vector![1.0, 2.0]);
let shape_pos = Isometry::new(vector![0.0, 1.0], 0.8);
let filter = QueryFilter::default();
query_pipeline.intersections_with_shape(rigid_body_set,
collider_set, &shape_pos, &shape, filter, |handle| {
println!("The collider {:?} intersects our shape.", handle);
true // Return `false` instead if we want to stop searching for other colliders that contain this point.
}
);
let aabb = Aabb::new(point![-1.0, -2.0], point![1.0, 2.0]);
query_pipeline.colliders_with_aabb_intersecting_aabb(&aabb, |handle| {
println!("The collider {:?} has an AABB intersecting our test AABB", handle);
true // Return `false` instead if we want to stop searching for other colliders that contain this point.
});
let shape = Cuboid::new(vector![1.0, 2.0, 3.0]);
let shape_pos = Isometry::new(vector![0.0, 1.0, 0.0], vector![0.2, 0.7, 0.1]);
let filter = QueryFilter::default();
query_pipeline.intersections_with_shape(
rigid_body_set, &collider_set, &shape_pos, &shape, filter, |handle| {
println!("The collider {:?} intersects our shape.", handle);
true // Return `false` instead if we want to stop searching for other colliders that contain this point.
}
);
let aabb = Aabb::new(point![-1.0, -2.0, -3.0], point![1.0, 2.0, 3.0]);
query_pipeline.colliders_with_aabb_intersecting_aabb(&aabb, |handle| {
println!("The collider {:?} has an AABB intersecting our test AABB", handle);
true // Return `false` instead if we want to stop searching for other colliders that contain this point.
});
It is possible to only apply the scene query to a subsets of the colliders using a query filter
Query filters
It is common to exclude some colliders from being considered by a scene query. For example, a ray-cast performed for a
character controller will usually want to skip the character itself. Sometimes, we may even want it to ignore both the
character and any collider attached to a dynamic rigid-body, and ignore all sensors. To allow this filtering, most
scene queries take a QueryFilter
argument that lets you describe what needs to be excluded. In particular
its fields:
flags
allows you to discard whole families of colliders based on their types or their parent types (e.g. exclude all sensors and all the colliders attached to a dynamic rigid-body).groups
is used to apply the collision group rules for the scene query. The scene query will only consider hits with colliders with collision groups compatible with this collision group (using the bitwise test described in the collision groups section).exclude_collider
is the handle of one collider the query must ignore.exclude_rigid_body
is the handle of one rigid-body with attached colliders the query must ignore.predicate
is a user-defined closure to apply any filtering rule. This can be used if the other filtering options above are not flexible enough.
Here is an an example of usage of the query filters with ray-casting:
let ray = Ray::new(point![1.0, 2.0], vector![0.0, 1.0]);
let max_toi = 4.0;
let solid = true;
let filter = QueryFilter::exclude_dynamic()
.exclude_sensors()
.exclude_rigid_body(player_handle)
.groups(InteractionGroups::new(
Group::GROUP_1 | Group::GROUP_2,
Group::GROUP_1,
))
.predicate(&|handle, collider| collider.user_data == 10);
if let Some((handle, toi)) =
query_pipeline.cast_ray(&rigid_body_set, &collider_set, &ray, max_toi, solid, filter)
{
// Handle the hit.
}