Skip to main content

Scene queries

Scene queries are geometric queries that take all the colliders of the physics world into account. These queries are available through the QueryPipelineRapierContext (obtained from the ReadRapierContext system parameter)World classfunctions taking the R3World as their first argument (e.g. r3TryCastRay)QueryPipeline of the physics world.

The QueryPipeline is a temporary object obtained from the physics world with PhysicsWorld::query_pipeline (or PhysicsWorld::query_pipeline_with_filter). It reuses the acceleration data-structure (BVH) of the broad-phase, which is automatically updated by the physics stepping function. Therefore the scene queries take into account the positions of the colliders at the end of the last timestep:

// Game loop.
for _ in 0..10 {
// Stepping the simulation updates the broad-phase the scene queries rely on.
world.step();

// The scene queries take into account the positions of the colliders at the end of
// the last timestep.
let query_pipeline = world.query_pipeline();
// Run the scene queries with `query_pipeline` here.
}

The RapierContext is obtained with ReadRapierContext::single (or WriteRapierContext::single). Its scene queries reuse the acceleration data-structure (BVH) of the broad-phase, which is automatically updated by each simulation step. Therefore the scene queries take into account the positions of the colliders at the end of the last timestep: a Transform modified since then won't be taken into account before the next step.

Each scene query of RapierContext creates a temporary RapierQueryPipeline configured by its QueryFilter argument. If you need to run several queries with the same filter, or if you prefer iterators rather than closures for the queries returning multiple results, that RapierQueryPipeline can be accessed directly with RapierContext::with_query_pipeline. It also exposes a few other queries not detailed in this guide (e.g. project_point_and_get_feature, distance_to_shape, closest_points_to_shape, contact_with_shape, and the bvh itself for custom traversals):

/* Run several scene queries sharing the same filter inside of a system. */
fn run_queries_with_pipeline(rapier_context: ReadRapierContext) {
let rapier_context = rapier_context.single().unwrap();
let filter = QueryFilter::exclude_dynamic();

rapier_context.with_query_pipeline(filter, |query_pipeline| {
// The scene queries take into account the positions of the colliders at the end of
// the last timestep.
let ray_pos = Vec2::new(1.0, 2.0);
let ray_dir = Vec2::new(0.0, -1.0);
if let Some((entity, toi)) = query_pipeline.cast_ray(ray_pos, ray_dir, 4.0, true) {
println!(
"Entity {:?} hit at point {}",
entity,
ray_pos + ray_dir * toi
);
}

// The methods of the `RapierQueryPipeline` return iterators instead of
// calling a closure for each result.
for (entity, collider) in query_pipeline.intersect_point(ray_pos) {
println!(
"The entity {:?} contains the point. Is it a sensor? {}",
entity,
collider.is_sensor()
);
}
});
}

The queries involving a shape (e.g. intersect_shape, cast_shape, or contact_with_shape) accept any type implementing the AsShape trait: a Collider can be given directly, as well as any Rapier shape (e.g. a Ball, or &*shared_shape for a SharedShape). Note that the scene queries rely on the query dispatcher of the physics context, so a custom dispatcher given to RapierContextSimulation::set_query_dispatcher is taken into account by the scene queries as well as by the character controller.

The scene queries reuse the acceleration data-structure (BVH) of the broad-phase, which is automatically updated by r3Step (and by r3DetectCollisions). Therefore the scene queries take into account the positions of the colliders at the end of the last timestep: a collider moved since then (e.g. with r3Collider_SetTranslation) may not be found at its new position before the next step:

// Game loop.
for (int i = 0; i < 10; i++) {
// Stepping the simulation updates the broad-phase the scene queries rely on.
r2Step(world, NULL, NULL);

// The scene queries take into account the positions of the colliders at the end of
// the last timestep. Run the scene queries on `world` here.
}

Every scene query takes a pointer to an R3QueryOptions, which selects the colliders taken into account (see the query filters section). A NULL pointer applies the default options, which don't exclude any collider. The queries involving a shape (e.g. r3IntersectShape or r3TryCastShape) take an R3SharedShape, created by one of the shared-shape constructors (e.g. r3CuboidSharedShape) or cloned from the shape of an existing collider with r3Collider_CloneShape. This shape is owned by the application: it must be freed with r3FreeSharedShape once it is no longer needed.

The queries finding several colliders copy their results into a buffer given by the application, together with its capacity. Calling them with a NULL buffer and a zero capacity gives the number of results, then a second call with a buffer large enough copies them. If the buffer is too small, nothing is copied: the query returns the required number of elements and reports the R3_BUFFER_TOO_SMALL error.

The QueryPipeline is given by the PhysicsWorld.query_pipeline property, which returns the same object every time. It reuses the acceleration data-structure (BVH) of the broad-phase, which is automatically updated by PhysicsWorld.step. Therefore the scene queries take into account the positions of the colliders at the end of the last timestep: a collider inserted or moved since then (e.g. by setting its translation) may not be found at its new position before the next step (a removed collider is never returned though):

# Game loop.
for _ in range(10):
# Stepping the simulation updates the broad-phase the scene queries rely on.
world.step()

# The scene queries take into account the positions of the colliders at the end of
# the last timestep.
query_pipeline = world.query_pipeline
# Run the scene queries with `query_pipeline` here.

If the scene queries must see the colliders inserted, moved, or re-shaped since the last step without waiting for the next one, PhysicsWorld.update_query_pipeline refreshes the BVH of the broad-phase. This doesn't interfere with the next step (which still processes these changes), but it recomputes the AABB of every enabled collider, so it shouldn't be called when the queries are run right after a step.

Every scene query takes an optional filter argument, a QueryFilter which selects the colliders taken into account (see the query filters section). The queries involving a shape (e.g. QueryPipeline.intersect_shape or QueryPipeline.cast_shape) take a SharedShape, created by one of its constructors (e.g. SharedShape.cuboid) or given by the shape property of an existing collider. Finally, the queries finding several colliders don't return a list: they call a function given as argument once per collider found, and stop the search as soon as this function returns False.

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.

info

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 below). The more results you get, the more computationally expensive the ray-cast will be.

let ray = Ray::new(Vector::new(1.0, 2.0), Vector::new(0.0, 1.0));
let max_toi = 4.0;
let solid = true;
let filter = QueryFilter::default();

let query_pipeline = world.query_pipeline_with_filter(filter);

if let Some((handle, toi)) = query_pipeline.cast_ray(
&ray, max_toi, solid
) {
// 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(
&ray, max_toi, solid
) {
// 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);
}

for (handle, _, intersection) in query_pipeline.intersect_ray(ray, max_toi, solid) {
// 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);
}
/* Cast a ray inside of a system. */
fn cast_ray(rapier_context: ReadRapierContext) {
let rapier_context = rapier_context.single().unwrap();
let ray_pos = Vec2::new(1.0, 2.0);
let ray_dir = Vec2::new(0.0, 1.0);
let max_toi = 4.0;
let solid = true;
let filter = QueryFilter::default();

if let Some((entity, toi)) = rapier_context.cast_ray(ray_pos, ray_dir, max_toi, solid, filter) {
// The first collider hit has the entity `entity` and it hit after
// the ray travelled a distance equal to `ray_dir * toi`.
let hit_point = ray_pos + ray_dir * toi;
println!("Entity {:?} hit at point {}", entity, hit_point);
}

if let Some((entity, intersection)) =
rapier_context.cast_ray_and_get_normal(ray_pos, ray_dir, max_toi, solid, filter)
{
// This is similar to `RapierContext::cast_ray` illustrated above except
// that it also returns the normal of the collider shape at the hit point.
let hit_point = intersection.point;
let hit_normal = intersection.normal;
println!(
"Entity {:?} hit at point {} with normal {}",
entity, hit_point, hit_normal
);
}

rapier_context.intersect_ray(
ray_pos,
ray_dir,
max_toi,
solid,
filter,
|entity, _collider, intersection| {
// Callback called on each collider hit by the ray.
let hit_point = intersection.point;
let hit_normal = intersection.normal;
println!(
"Entity {:?} hit at point {} with normal {}",
entity, hit_point, hit_normal
);
true // Return `false` instead if we want to stop searching for other hits.
},
);
}

The results identify the collider hit by the entity it is attached to. The resulting RayIntersection contains the world-space hit point and normal, as well as the index of the part of the shape that was hit (subshape) for shapes composed of several pieces (compound shapes, triangle meshes, polylines, heightfields, voxels). The closure given to RapierContext::intersect_ray is also given the Rapier collider (rapier::geometry::Collider, not to be confused with the Collider component) that was hit, which gives access to its shape, position, parent rigid-body, etc., without needing an additional ECS query. Returning false from that closure stops the search for other hits.

let ray = new RAPIER.Ray({ x: 1.0, y: 2.0 }, { x: 0.0, y: 1.0 });
let maxToi = 4.0;
let solid = true;

let hit = world.castRay(ray, maxToi, solid);
if (hit != null) {
// The first collider hit has the handle `hit.colliderHandle` and it hit after
// the ray travelled a distance equal to `ray.dir * toi`.
let hitPoint = ray.pointAt(hit.timeOfImpact); // Same as: `ray.origin + ray.dir * toi`
console.log("Collider", hit.collider, "hit at point", hitPoint);
}

let hitWithNormal = world.castRayAndGetNormal(ray, maxToi, solid);
if (hitWithNormal != null) {
// 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 hitPoint = ray.pointAt(hitWithNormal.timeOfImpact);
console.log("Collider", hitWithNormal.collider, "hit at point", hitPoint, "with normal", hitWithNormal.normal);
}

world.intersectionsWithRay(ray, maxToi, solid, (hit) => {
// Callback called on each collider hit by the ray.
let hitPoint = ray.pointAt(hit.timeOfImpact);
console.log("Collider", hit.collider, "hit at point", hitPoint, "with normal", hit.normal);
return true; // Return `false` instead if we want to stop searching for other hits.
});
R2Vector ray_origin = r2Vector(1.0, 2.0);
R2Vector ray_dir = r2Vector(0.0, 1.0);
R2Real max_toi = 4.0;
R2Bool solid = 1;
R2QueryOptions options = r2DefaultQueryOptions();

R2RayToi toi = r2CastRayToi(world, &options, ray_origin, ray_dir, max_toi, solid);
if (toi.found) {
// The first collider hit has the handle `toi.collider` and it hit after
// the ray travelled a distance equal to `ray_dir * toi.toi`.
R2Vector hit_point = r2VectorAdd(ray_origin, r2VectorScale(ray_dir, toi.toi));
printf("Collider %u hit at point (%f, %f)\n", toi.collider.index, (double)hit_point.x,
(double)hit_point.y);
}

R2OptionalRayHit result = r2TryCastRay(world, &options, ray_origin, ray_dir, max_toi, solid);
if (result.found) {
R2RayHit hit = result.hit;
// This is similar to `r2CastRayToi` illustrated above except
// that it also returns the normal of the collider shape at the hit point.
R2Vector hit_point = r2VectorAdd(ray_origin, r2VectorScale(ray_dir, hit.time_of_impact));
R2Vector hit_normal = hit.normal;
printf("Collider %u hit at point (%f, %f) with normal (%f, %f)\n",
hit.collider.index, (double)hit_point.x, (double)hit_point.y,
(double)hit_normal.x, (double)hit_normal.y);
}

r3CastRayToi only gives the handle of the first collider hit and the time-of-impact, whereas r3TryCastRay also gives the world-space normal of the collider's shape at the hit point, as well as the feature of the shape that was hit (a vertex, an edge, or a face, identified by feature_type and feature_id). Both set the found field of their result to 0 if the ray doesn't hit anything. Note that r3CastRay gives the same result as r3TryCastRay but reports a miss as the R3_NOT_FOUND error: it is only suitable if the ray is expected to always hit something.

Finally, r3IntersectRay gives the hits of every collider intersected by the ray (in no particular order), with the same details as r3TryCastRay:

// Get the number of colliders hit by the ray, then copy all their hits.
size_t count = r2IntersectRay(world, &options, ray_origin, ray_dir, max_toi, solid, NULL, 0);
R2RayHit *hits = malloc(count * sizeof(*hits));
count = r2IntersectRay(world, &options, ray_origin, ray_dir, max_toi, solid, hits, count);

for (size_t i = 0; i < count; i++) {
// Loop on each collider hit by the ray.
R2Vector hit_point = r2VectorAdd(ray_origin, r2VectorScale(ray_dir, hits[i].time_of_impact));
R2Vector hit_normal = hits[i].normal;
printf("Collider %u hit at point (%f, %f) with normal (%f, %f)\n",
hits[i].collider.index, (double)hit_point.x, (double)hit_point.y,
(double)hit_normal.x, (double)hit_normal.y);
}
free(hits);
ray = rp.Ray(origin=(1.0, 2.0, 3.0), dir=(0.0, 1.0, 0.0))
max_toi = 4.0
solid = True
query_filter = rp.QueryFilter()

query_pipeline = world.query_pipeline

hit = query_pipeline.cast_ray(ray, max_toi, solid, filter=query_filter)
if hit is not None:
handle, toi = hit
# The first collider hit has the handle `handle` and it hit after
# the ray travelled a distance equal to `ray.dir * toi`.
hit_point = ray.point_at(toi) # Same as: `ray.origin + ray.dir * toi`
print(f"Collider {handle} hit at point {hit_point}")

hit = query_pipeline.cast_ray_and_get_normal(ray, max_toi, solid, filter=query_filter)
if hit is not None:
handle, intersection = hit
# This is similar to `QueryPipeline.cast_ray` illustrated above except
# that it also returns the normal of the collider shape at the hit point.
hit_point = ray.point_at(intersection.time_of_impact)
hit_normal = intersection.normal
print(f"Collider {handle} hit at point {hit_point} with normal {hit_normal}")


def on_ray_hit(handle, intersection):
# Callback called on each collider hit by the ray.
hit_point = ray.point_at(intersection.time_of_impact)
hit_normal = intersection.normal
print(f"Collider {handle} hit at point {hit_point} with normal {hit_normal}")
return True # Return `False` to stop the search.


query_pipeline.intersect_ray(ray, max_toi, solid, on_ray_hit, filter=query_filter)

QueryPipeline.cast_ray only gives the handle of the first collider hit and the time-of-impact, whereas QueryPipeline.cast_ray_and_get_normal also gives a RayIntersection with the world-space normal of the collider's shape at the hit point, as well as the feature of the shape that was hit (a vertex, an edge, or a face, identified by a FeatureId). Both return None if the ray doesn't hit anything. Finally, QueryPipeline.intersect_ray calls the given function with the handle and the RayIntersection of every collider intersected by the ray (in no particular order), until this function returns False.

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 maxToi max_toi 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 at ray.originorigin moving at a linear velocity equal to ray.dirdirection. Therefore, max_toi limits the ray-cast to the segment: [ray.origin, ray.origin + ray.dir * max_toi][origin, origin + direction * max_toi].
  • solid: this argument controls the behavior of the ray-cast if ray.originorigin is inside of a shape: if solid is truetrue1True 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. If solid is falsefalse0False 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:

solid ray-cast

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.

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 filter argumentsmax_toi and query options arguments in the ray-casting section.

The shape-casting along a straight line is performed by QueryPipeline::cast_shapeRapierContext::cast_shapeWorld.castShaper3TryCastShapeQueryPipeline.cast_shape. This method has similar arguments as QueryPipeline::cast_rayRapierContext::cast_rayWorld.castRayr3TryCastRayQueryPipeline.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.originorigin) and the linear velocity the shape is travelling at (this is analog to ray.dirdirection), and the max_toi is replaced by the R3ShapeCastOptions, and the max_toi is replaced by a ShapeCastOptions:

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);
}
/* 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
);
}
}
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);
}
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.

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_impact plays the role of the max_toi of the ray-casts: the shape travels at most shape_vel * max_time_of_impact. It is unbounded by default, and ShapeCastOptions.with_max_time_of_impact gives the default options with a finite 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 True (the default), that collider is reported with a time-of-impact equal to zero. If it is False, 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.

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 the handle of the first collider being hitthe entity of the first collider being hitthe first collider being hit (hit.collider)the handle of the first collider being hit (hit.collider)the handle of the first collider being hit, 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_impactshapeVel * hit.time_of_impactshape_vel * hit.time_of_impactshape_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_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_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 NonlinearRigidMotionNonlinearMotion which contains the initial pose of the shape, its linear and angular velocities, and the local-space point around which the shape rotates. 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. 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_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) (the details are None if the hit reported is a penetration at start_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.

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.

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 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. 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.

Point projection​

Point projection will either project a point on the closest collider of the scene (QueryPipeline::project_pointRapierContext::project_pointWorld.projectPointr3TryProjectPointQueryPipeline.project_point), or will enumerate every collider containing given point (QueryPipeline::intersect_pointRapierContext::intersect_pointWorld.intersectionsWithPointr3IntersectPointQueryPipeline.intersect_point).

let point = Vector::new(1.0, 2.0);
let solid = true;
let max_dist = 12.0;
let filter = QueryFilter::default();

let query_pipeline = world.query_pipeline_with_filter(filter);

if let Some((handle, projection)) = query_pipeline.project_point(
point, max_dist, solid
) {
// 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);
}

for (handle, _) in query_pipeline.intersect_point(point) {
// Callback called on each collider with a shape containing the point.
println!("The collider {:?} contains the point.", handle);
}
/* Project a point inside of a system. */
fn project_point(rapier_context: ReadRapierContext) {
let rapier_context = rapier_context.single().unwrap();
let point = Vec2::new(1.0, 2.0);
let max_dist = 4.0; // Colliders further than this distance are ignored.
let solid = true;
let filter = QueryFilter::default();

if let Some((entity, projection)) = rapier_context.project_point(point, max_dist, solid, filter)
{
// The collider closest to the point is attached to `entity`.
println!(
"Projected point on entity {:?}. Point projection: {}",
entity, projection.point
);
println!(
"Point was inside of the collider shape: {}",
projection.is_inside
);
}

rapier_context.intersect_point(point, filter, |entity, _collider| {
// Callback called on each collider with a shape containing the point.
println!("The entity {:?} contains the point.", entity);
// Return `false` instead if we want to stop searching for other colliders containing this point.
true
});
}

The resulting PointProjection also contains the index of the part of the shape the point was projected on (subshape) for shapes composed of several pieces (compound shapes, triangle meshes, etc.) Just like for ray-casting, the closure given to RapierContext::intersect_point is given the entity of each collider containing the point, as well as its Rapier collider, and can return false to stop the search.

let point = { x: 1.0, y: 2.0 };
let solid = true;

let proj = world.projectPoint(point, solid);
if (proj != null) {
// The collider closest to the point has this `handle`.
console.log("Projected point on collider ", proj.collider, ". Point projection: ", proj.point);
console.log("Point was inside of the collider shape: {}", proj.isInside);
}

world.intersectionsWithPoint(point, (handle) => {
// Callback called on each collider with a shape containing the point.
console.log("The collider", handle, "contains the point.");
// Return `false` instead if we want to stop searching for other colliders containing this point.
return true;
});
R2Vector point = r2Vector(1.0, 2.0);
R2Bool solid = 1;
R2Real max_dist = 12.0;
R2QueryOptions options = r2DefaultQueryOptions();

R2OptionalPointProjection result = r2TryProjectPoint(world, &options, point, max_dist, solid);
if (result.found) {
R2PointProjection projection = result.projection;
// The collider closest to the point has the handle `projection.collider`.
printf("Projected point on collider %u. Point projection: (%f, %f)\n", projection.collider.index,
(double)projection.point.x, (double)projection.point.y);
printf("Point was inside of the collider shape: %u\n", projection.is_inside);
}

// Get the number of colliders containing the point, then copy their handles.
size_t count = r2IntersectPoint(world, &options, point, NULL, 0);
R2ColliderHandle *handles = malloc(count * sizeof(*handles));
count = r2IntersectPoint(world, &options, point, handles, count);
for (size_t i = 0; i < count; i++) {
// Loop on each collider with a shape containing the point.
printf("The collider %u contains the point.\n", handles[i].index);
}
free(handles);

The resulting R3PointProjection (the projection field of the result) contains the handle of the collider the point was projected on, the projected point (in world-space), and whether the original point was inside of that collider (is_inside). If the point is inside of a shape, solid controls the result just like for ray-casting: with solid set to 1 the point is its own projection, whereas with solid set to 0 it is projected on the boundary of the shape. r3TryProjectPoint sets the found field of its result to 0 if no collider is closer than max_dist, whereas r3ProjectPoint reports this as the R3_NOT_FOUND error. Finally, r3IntersectPoint copies the handles of the colliders containing the point into a buffer given by the application, as described at the beginning of this page.

point = (1.0, 2.0, 3.0)
solid = True
max_dist = 12.0
query_filter = rp.QueryFilter()

query_pipeline = world.query_pipeline

projection = query_pipeline.project_point(point, solid, filter=query_filter, max_dist=max_dist)
if projection is not None:
handle, projection = projection
# The collider closest to the point has this `handle`.
print(f"Projected point on collider {handle}. Point projection: {projection.point}")
print(f"Point was inside of the collider shape: {projection.is_inside}")

def on_point_intersection(handle):
# Callback called on each collider with a shape containing the point.
print(f"The collider {handle} contains the point.")
return True # Return `False` to stop the search.

query_pipeline.intersect_point(point, on_point_intersection, filter=query_filter)

QueryPipeline.project_point returns None if no collider is closer than max_dist (which is unbounded if it isn't given), and the handle of the collider the point was projected on, together with a PointProjection, otherwise. This PointProjection contains the projected point (in world-space), and whether the original point was inside of that collider (is_inside). If the point is inside of a shape, solid controls the result just like for ray-casting: with solid set to True the point is its own projection, whereas with solid set to False it is projected on the boundary of the shape. QueryPipeline.project_point_and_get_feature also gives the FeatureId of the part of the shape (vertex, edge, or face) the point was projected on. Finally, QueryPipeline.intersect_point calls the given function with the handle of each collider containing the point, until this function returns False.

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::intersect_shapeRapierContext::intersect_shapeWorld.intersectionsWithShaper3IntersectShapeQueryPipeline.intersect_shape searches for all the colliders with shapes intersecting the given shape.
  • The approximate intersection test QueryPipeline::intersect_aabb_conservativeRapierContext::intersect_aabb_conservativeWorld.collidersWithAabbIntersectingAabbr3IntersectAabbConservativeQueryPipeline.intersect_aabb_conservative 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. Note that the AABB taken into account is the one currently stored in the BVH of the broad-phase: it isn't recomputed from the latest collider positions. Note that the AABB taken into account is the one currently stored in the BVH of the broad-phase (updated by each simulation step): it isn't recomputed from the latest collider positions. Note that the AABB taken into account is the one currently stored in the BVH of the broad-phase (updated by each call to r3Step): it isn't recomputed from the latest collider positions. Note that the AABB taken into account is the one currently stored in the BVH of the broad-phase (updated by each call to PhysicsWorld.step): it isn't recomputed from the latest collider positions.
info

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.

let shape = Cuboid::new(Vector::new(1.0, 2.0));
let shape_pos = Pose::new(Vector::new(0.0, 1.0), 0.2);
let filter = QueryFilter::default();

let query_pipeline = world.query_pipeline_with_filter(filter);

for (handle, _) in query_pipeline.intersect_shape(shape_pos, &shape) {
println!("The collider {:?} intersects our shape.", handle);
}

let aabb = Aabb::new(Vector::new(-1.0, -2.0), Vector::new(1.0, 2.0));
for (handle, _) in query_pipeline.intersect_aabb_conservative(aabb) {
println!("The collider {:?} has an AABB intersecting our test AABB", handle);
}
/* Test intersections inside of a system. */
fn test_intersections(rapier_context: ReadRapierContext) {
let rapier_context = rapier_context.single().unwrap();
let shape = Collider::cuboid(1.0, 2.0);
let shape_pos = Vec2::new(0.0, 1.0);
let shape_rot = 0.8;
let filter = QueryFilter::default();

rapier_context.intersect_shape(shape_pos, shape_rot, &shape, filter, |entity, _collider| {
println!("The entity {:?} intersects our shape.", entity);
true // Return `false` instead if we want to stop searching for other colliders intersecting our shape.
});

let aabb = Aabb2d::new(Vec2::new(-1.0, -2.0), Vec2::new(1.0, 2.0));
rapier_context.intersect_aabb_conservative(aabb, filter, |entity, _collider| {
println!(
"The entity {:?} has an AABB intersecting our test AABB",
entity
);
true // Return `false` instead if we want to stop searching for other colliders with an intersecting AABB.
});
}

The closures given to these methods are called with the entity of each collider found, as well as its Rapier collider (rapier::geometry::Collider), and can return false to stop the search. The AABB to test is given as a Bevy Aabb2d in 2D, or Aabb3d in 3D.

let shape = new RAPIER.Cuboid(1.0, 2.0);
let shapePos = { x: 1.0, y: 2.0 };
let shapeRot = 0.1;

world.intersectionsWithShape(shapePos, shapeRot, shape, (handle) => {
console.log("The collider", handle, "intersects our shape.");
return true; // Return `false` instead if we want to stop searching for other colliders that contain this point.
});

let aabbCenter = { x: -1.0, y: -2.0 };
let aabbHalfExtents = { x: 0.5, y: 0.6 };
world.collidersWithAabbIntersectingAabb(aabbCenter, aabbHalfExtents, (handle) => {
console.log("The collider", handle, "has an AABB intersecting our test AABB");
return true; // Return `false` instead if we want to stop searching for other colliders that contain this point.
});
R2SharedShape *shape = r2CuboidSharedShape(r2Vector(1.0, 2.0));
R2Pose shape_pos = r2Pose(r2Vector(0.0, 1.0), r2Rotation(0.2));
R2QueryOptions options = r2DefaultQueryOptions();

// Get the number of colliders intersecting the shape, then copy their handles.
size_t count = r2IntersectShape(world, &options, shape_pos, shape, NULL, 0);
R2ColliderHandle *handles = malloc(count * sizeof(*handles));
count = r2IntersectShape(world, &options, shape_pos, shape, handles, count);
for (size_t i = 0; i < count; i++) {
printf("The collider %u intersects our shape.\n", handles[i].index);
}
free(handles);
r2FreeSharedShape(shape);

R2Aabb aabb = {r2Vector(-1.0, -2.0), r2Vector(1.0, 2.0)};
count = r2IntersectAabbConservative(world, &options, aabb, NULL, 0);
handles = malloc(count * sizeof(*handles));
count = r2IntersectAabbConservative(world, &options, aabb, handles, count);
for (size_t i = 0; i < count; i++) {
printf("The collider %u has an AABB intersecting our test AABB.\n", handles[i].index);
}
free(handles);

Both functions copy the handles of the colliders found into a buffer given by the application, as described at the beginning of this page. The AABB to test is an R3Aabb, given by its minimum (mins) and maximum (maxs) corners.

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)))
query_filter = rp.QueryFilter()

query_pipeline = world.query_pipeline


def on_shape_intersection(handle):
print(f"The collider {handle} intersects our shape.")
return True # Return `False` to stop the search.


query_pipeline.intersect_shape(shape_pos, shape, on_shape_intersection, filter=query_filter)

aabb = rp.Aabb(mins=(-1.0, -2.0, -3.0), maxs=(1.0, 2.0, 3.0))


def on_aabb_intersection(handle):
print(f"The collider {handle} has an AABB intersecting our test AABB.")
return True # Return `False` to stop the search.


query_pipeline.intersect_aabb_conservative(aabb, on_aabb_intersection, filter=query_filter)

Both methods call the given function with the handle of each collider found, until this function returns False. The AABB to test is an Aabb, given by its minimum (mins) and maximum (maxs) corners. If you only need to know whether at least one collider has an AABB intersecting the given AABB, QueryPipeline.test_aabb returns this as a boolean.

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:several optional arguments that lets you describe what needs to be excluded. In particular, the arguments:an R3QueryOptions argument that lets you describe what needs to be excluded. In particular the fields of its filter (an R3QueryFilter), and its predicate:an optional filter argument, a QueryFilter that lets you describe what needs to be excluded. In particular the keyword arguments of its constructor:

  • 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 closurecallback to apply any filtering rule. This can be used if the other filtering options above are not flexible enough.

The query options are initialized by r3DefaultQueryOptions, which doesn't exclude any collider. The flags are a combination of the R3_QUERY_EXCLUDE_* constants (e.g. R3_QUERY_EXCLUDE_SENSORS), or one of the shortcuts R3_QUERY_ONLY_DYNAMIC, R3_QUERY_ONLY_KINEMATIC, and R3_QUERY_ONLY_FIXED. The groups are only applied if the use_groups field is set to 1. The exclude_collider and exclude_rigid_body fields are set to an invalid handle (e.g. R3_INVALID_COLLIDER_HANDLE) to exclude nothing. Finally, the predicate is a callback called for each collider that passed the other filtering rules: it returns 0 to exclude that collider. It is given the userData field of the query options, a read-only access to the world (an R3ReadContext to be given to the r3ReadCollider_* and r3ReadRigidBody_* functions), and the handle of the collider. Other scene queries can be performed from this callback, but the world cannot be modified until the outer query returns.

The exclude_collider and exclude_rigid_body fields are set to the entity of the collider or rigid-body to exclude (instead of its handle). The predicate is given the entity of each collider as well as its Rapier collider (rapier::geometry::Collider), so its shape, position, or parent can be read without any additional ECS query. Since the filter only holds a reference to the predicate closure, that closure can borrow other system parameters, e.g., a Query for reading the components of the collider's entity.

QueryFilter() doesn't exclude any collider. The flags are a combination (with the | operator) of the QueryFilterFlags constants, e.g., QueryFilterFlags.EXCLUDE_SENSORS or QueryFilterFlags.ONLY_DYNAMIC. The filters are generally built with the static methods of QueryFilter setting its flags (e.g. QueryFilter.exclude_dynamic or QueryFilter.only_fixed), followed by its builder methods (exclude_sensors, exclude_solids, groups, exclude_collider, exclude_rigid_body, and predicate) which return a new filter so that they can be chained. Finally, the predicate is a function called for each collider that passed the other filtering rules: it is given the handle of the collider and a view of the Collider itself, and returns False to exclude that collider. It can read any property of the collider (e.g. its user_data), but must not modify the world. An exception raised by the predicate is raised again by the scene query.

Here is an an example of usage of the query filters with ray-casting:

let ray = Ray::new(Vector::new(1.0, 2.0), Vector::new(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,
InteractionTestMode::And,
))
.predicate(&|handle, collider| collider.user_data == 10);
let query_pipeline = world.query_pipeline_with_filter(filter);

if let Some((handle, toi)) = query_pipeline.cast_ray(&ray, max_toi, solid) {
// Handle the hit.
}
/* Cast a ray inside of a system. */
fn cast_ray_filtered(
rapier_context: ReadRapierContext,
player_query: Query<Entity, With<Player>>,
custom_data_query: Query<&CustomData>,
) {
let rapier_context = rapier_context.single().unwrap();
let player_handle = player_query.single().unwrap();
let ray_pos = Vec2::new(1.0, 2.0);
let ray_dir = Vec2::new(0.0, 1.0);
let max_toi = 4.0;
let solid = true;
let predicate = |entity, _collider: &_| {
// We can use a query to bevy inside the predicate.
custom_data_query
.get(entity)
.is_ok_and(|custom_data| custom_data.data == 10)
};
let filter = QueryFilter::exclude_dynamic()
.exclude_sensors()
.exclude_rigid_body(player_handle)
.groups(CollisionGroups::new(
Group::GROUP_1 | Group::GROUP_2,
Group::GROUP_1,
))
.predicate(&predicate);

if let Some((entity, toi)) = rapier_context.cast_ray(ray_pos, ray_dir, max_toi, solid, filter) {
// Handle the hit.
}
}
let ray = new RAPIER.Ray({ x: 1.0, y: 2.0 }, { x: 0.0, y: 1.0 });
let maxToi = 4.0;
let solid = true;

let filterFlags = QueryFilterFlags.EXCLUDE_DYNAMIC;
let filterGroups = 0x000b0001;
let filterExcludeRigidBody = player_rigid_body;
let filterPredicate = (collider: Collider) => data.get(collider.handle) == 10.0;

let hit = world.castRay(ray, maxToi, solid, filterFlags, filterGroups, null, filterExcludeRigidBody, filterPredicate);
if (hit != null) {
// Handle the hit.
}
// The predicate is called for each collider that passed the other filtering rules.
// Returning 0 excludes the collider from the scene query.
static R2Bool RAPIER_CALL user_data_predicate(void *user_data, const R2ReadContext *read,
R2ColliderHandle handle) {
(void)user_data;
return r2ReadCollider_UserData(read, handle).low == 10;
}

static void query_filter_section(const R2World *world, R2RigidBodyHandle player_handle) {
R2Vector ray_origin = r2Vector(1.0, 2.0);
R2Vector ray_dir = r2Vector(0.0, 1.0);
R2Real max_toi = 4.0;
R2Bool solid = 1;
R2QueryOptions options = r2DefaultQueryOptions();
options.filter.flags = R2_QUERY_EXCLUDE_DYNAMIC | R2_QUERY_EXCLUDE_SENSORS;
options.filter.exclude_rigid_body = player_handle;
options.filter.use_groups = 1;
options.filter.groups.memberships = 0x0001 | 0x0002; // Groups 1 and 2.
options.filter.groups.filter = 0x0001; // Group 1.
options.filter.groups.test_mode = R2_GROUPS_AND;
options.predicate = user_data_predicate;
options.userData = NULL; // Given to the predicate as its first argument.

R2RayToi toi = r2CastRayToi(world, &options, ray_origin, ray_dir, max_toi, solid);
if (toi.found) {
// Handle the hit.
}
}
ray = rp.Ray(origin=(1.0, 2.0, 3.0), dir=(0.0, 1.0, 0.0))
max_toi = 4.0
solid = True
query_filter = (
rp.QueryFilter.exclude_dynamic()
.exclude_sensors()
.exclude_rigid_body(player_handle)
.groups(
rp.InteractionGroups(
memberships=rp.Group.GROUP_1 | rp.Group.GROUP_2,
filter=rp.Group.GROUP_1,
test_mode=rp.InteractionTestMode.AND,
)
)
.predicate(lambda handle, collider: collider.user_data == 10)
)
query_pipeline = world.query_pipeline

hit = query_pipeline.cast_ray(ray, max_toi, solid, filter=query_filter)
if hit is not None:
handle, toi = hit
# Handle the hit.