rapier2d/pipeline/query_pipeline.rs
1use crate::dynamics::RigidBodyHandle;
2use crate::geometry::{Aabb, Collider, ColliderHandle, PointProjection, Ray, RayIntersection};
3use crate::geometry::{BroadPhaseBvh, InteractionGroups};
4use crate::math::{Pose, Real, Vector};
5use crate::{dynamics::RigidBodySet, geometry::ColliderSet};
6use parry::bounding_volume::BoundingVolume;
7use parry::partitioning::{Bvh, BvhNode};
8use parry::query::details::{NormalConstraints, ShapeCastOptions};
9use parry::query::{NonlinearRigidMotion, QueryDispatcher, RayCast, ShapeCastHit};
10use parry::shape::{CompositeShape, CompositeShapeRef, FeatureId, Shape, TypedCompositeShape};
11
12/// A query system for performing spatial queries on your physics world (raycasts, shape casts, intersections).
13///
14/// Think of this as a "search engine" for your physics world. Use it to answer questions like:
15/// - "What does this ray hit?"
16/// - "What colliders are near this point?"
17/// - "If I move this shape, what will it collide with?"
18///
19/// Get a QueryPipeline from your [`BroadPhaseBvh`] using [`as_query_pipeline()`](BroadPhaseBvh::as_query_pipeline).
20///
21/// # Example
22/// ```
23/// # use rapier3d::prelude::*;
24/// # let mut bodies = RigidBodySet::new();
25/// # let mut colliders = ColliderSet::new();
26/// # let broad_phase = BroadPhaseBvh::new();
27/// # let narrow_phase = NarrowPhase::new();
28/// # let ground = bodies.insert(RigidBodyBuilder::fixed());
29/// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
30/// let query_pipeline = broad_phase.as_query_pipeline(
31/// narrow_phase.query_dispatcher(),
32/// &bodies,
33/// &colliders,
34/// QueryFilter::default()
35/// );
36///
37/// // Cast a ray downward
38/// let ray = Ray::new(Vector::new(0.0, 10.0, 0.0), Vector::new(0.0, -1.0, 0.0));
39/// if let Some((handle, toi)) = query_pipeline.cast_ray(&ray, Real::MAX, false) {
40/// println!("Hit collider {:?} at distance {}", handle, toi);
41/// }
42/// ```
43#[derive(Copy, Clone)]
44pub struct QueryPipeline<'a> {
45 /// The query dispatcher for running geometric queries on leaf geometries.
46 pub dispatcher: &'a dyn QueryDispatcher,
47 /// A bvh containing collider indices at its leaves.
48 pub bvh: &'a Bvh,
49 /// Rigid-bodies potentially involved in the scene queries.
50 pub bodies: &'a RigidBodySet,
51 /// Colliders potentially involved in the scene queries.
52 pub colliders: &'a ColliderSet,
53 /// The query filters for controlling what colliders should be ignored by the queries.
54 pub filter: QueryFilter<'a>,
55}
56
57/// Same as [`QueryPipeline`] but holds mutable references to the body and collider sets.
58///
59/// This structure is generally obtained by calling [`BroadPhaseBvh::as_query_pipeline_mut`].
60/// This is useful for argument passing. Call `.as_ref()` for obtaining a `QueryPipeline`
61/// to run the scene queries.
62pub struct QueryPipelineMut<'a> {
63 /// The query dispatcher for running geometric queries on leaf geometries.
64 pub dispatcher: &'a dyn QueryDispatcher,
65 /// A bvh containing collider indices at its leaves.
66 pub bvh: &'a Bvh,
67 /// Rigid-bodies potentially involved in the scene queries.
68 pub bodies: &'a mut RigidBodySet,
69 /// Colliders potentially involved in the scene queries.
70 pub colliders: &'a mut ColliderSet,
71 /// The query filters for controlling what colliders should be ignored by the queries.
72 pub filter: QueryFilter<'a>,
73}
74
75impl QueryPipelineMut<'_> {
76 /// Downgrades the mutable reference to an immutable reference.
77 pub fn as_ref(&self) -> QueryPipeline<'_> {
78 QueryPipeline {
79 dispatcher: self.dispatcher,
80 bvh: self.bvh,
81 bodies: &*self.bodies,
82 colliders: &*self.colliders,
83 filter: self.filter,
84 }
85 }
86}
87
88impl CompositeShape for QueryPipeline<'_> {
89 fn map_part_at(
90 &self,
91 shape_id: u32,
92 f: &mut dyn FnMut(Option<&Pose>, &dyn Shape, Option<&dyn NormalConstraints>),
93 ) {
94 self.map_untyped_part_at(shape_id, f);
95 }
96 fn bvh(&self) -> &Bvh {
97 self.bvh
98 }
99}
100
101impl TypedCompositeShape for QueryPipeline<'_> {
102 type PartNormalConstraints = ();
103 type PartShape = dyn Shape;
104 fn map_typed_part_at<T>(
105 &self,
106 shape_id: u32,
107 mut f: impl FnMut(Option<&Pose>, &Self::PartShape, Option<&Self::PartNormalConstraints>) -> T,
108 ) -> Option<T> {
109 let (co, co_handle) = self.colliders.get_unknown_gen(shape_id)?;
110
111 if self.filter.test(self.bodies, co_handle, co) {
112 Some(f(Some(co.position()), co.shape(), None))
113 } else {
114 None
115 }
116 }
117
118 fn map_untyped_part_at<T>(
119 &self,
120 shape_id: u32,
121 mut f: impl FnMut(Option<&Pose>, &dyn Shape, Option<&dyn NormalConstraints>) -> T,
122 ) -> Option<T> {
123 let (co, co_handle) = self.colliders.get_unknown_gen(shape_id)?;
124
125 if self.filter.test(self.bodies, co_handle, co) {
126 Some(f(Some(co.position()), co.shape(), None))
127 } else {
128 None
129 }
130 }
131}
132
133impl BroadPhaseBvh {
134 /// Initialize a [`QueryPipeline`] for scene queries from this broad-phase.
135 pub fn as_query_pipeline<'a>(
136 &'a self,
137 dispatcher: &'a dyn QueryDispatcher,
138 bodies: &'a RigidBodySet,
139 colliders: &'a ColliderSet,
140 filter: QueryFilter<'a>,
141 ) -> QueryPipeline<'a> {
142 QueryPipeline {
143 dispatcher,
144 bvh: &self.tree,
145 bodies,
146 colliders,
147 filter,
148 }
149 }
150
151 /// Initialize a [`QueryPipelineMut`] for scene queries from this broad-phase.
152 pub fn as_query_pipeline_mut<'a>(
153 &'a self,
154 dispatcher: &'a dyn QueryDispatcher,
155 bodies: &'a mut RigidBodySet,
156 colliders: &'a mut ColliderSet,
157 filter: QueryFilter<'a>,
158 ) -> QueryPipelineMut<'a> {
159 QueryPipelineMut {
160 dispatcher,
161 bvh: &self.tree,
162 bodies,
163 colliders,
164 filter,
165 }
166 }
167}
168
169impl<'a> QueryPipeline<'a> {
170 fn id_to_handle<T>(&self, (id, data): (u32, T)) -> Option<(ColliderHandle, T)> {
171 self.colliders.get_unknown_gen(id).map(|(_, h)| (h, data))
172 }
173
174 /// Replaces [`Self::filter`] with different filtering rules.
175 pub fn with_filter(self, filter: QueryFilter<'a>) -> Self {
176 Self { filter, ..self }
177 }
178
179 /// Casts a ray through the world and returns the first collider it hits.
180 ///
181 /// This is one of the most common operations - use it for line-of-sight checks,
182 /// projectile trajectories, mouse picking, laser beams, etc.
183 ///
184 /// Returns `Some((handle, distance))` if the ray hits something, where:
185 /// - `handle` is which collider was hit
186 /// - `distance` is how far along the ray the hit occurred (time-of-impact)
187 ///
188 /// # Parameters
189 /// * `ray` - The ray to cast (origin + direction). Create with `Ray::new(origin, direction)`
190 /// * `max_toi` - Maximum distance to check. Use `Real::MAX` for unlimited range
191 /// * `solid` - If `true`, detects hits even if the ray starts inside a shape. If `false`,
192 /// the ray "passes through" from the inside until it exits
193 ///
194 /// # Example
195 /// ```
196 /// # use rapier3d::prelude::*;
197 /// # let mut bodies = RigidBodySet::new();
198 /// # let mut colliders = ColliderSet::new();
199 /// # let broad_phase = BroadPhaseBvh::new();
200 /// # let narrow_phase = NarrowPhase::new();
201 /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
202 /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
203 /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
204 /// // Raycast downward from (0, 10, 0)
205 /// let ray = Ray::new(Vector::new(0.0, 10.0, 0.0), Vector::new(0.0, -1.0, 0.0));
206 /// if let Some((handle, toi)) = query_pipeline.cast_ray(&ray, Real::MAX, true) {
207 /// let hit_point = ray.origin + ray.dir * toi;
208 /// println!("Hit at {:?}, distance = {}", hit_point, toi);
209 /// }
210 /// ```
211 #[profiling::function]
212 pub fn cast_ray(
213 &self,
214 ray: &Ray,
215 max_toi: Real,
216 solid: bool,
217 ) -> Option<(ColliderHandle, Real)> {
218 CompositeShapeRef(self)
219 .cast_local_ray(ray, max_toi, solid)
220 .and_then(|hit| self.id_to_handle(hit))
221 }
222
223 /// Casts a ray and returns detailed information about the hit (including surface normal).
224 ///
225 /// Like [`cast_ray()`](Self::cast_ray), but returns more information useful for things like:
226 /// - Decals (need surface normal to orient the texture)
227 /// - Bullet holes (need to know what part of the mesh was hit)
228 /// - Ricochets (need normal to calculate bounce direction)
229 ///
230 /// Returns `Some((handle, intersection))` where `intersection` contains:
231 /// - `toi`: Distance to impact
232 /// - `normal`: Surface normal at the hit point
233 /// - `feature`: Which geometric feature was hit (vertex, edge, face)
234 ///
235 /// # Example
236 /// ```
237 /// # use rapier3d::prelude::*;
238 /// # let mut bodies = RigidBodySet::new();
239 /// # let mut colliders = ColliderSet::new();
240 /// # let broad_phase = BroadPhaseBvh::new();
241 /// # let narrow_phase = NarrowPhase::new();
242 /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
243 /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
244 /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
245 /// # let ray = Ray::new(Vector::new(0.0, 10.0, 0.0), Vector::new(0.0, -1.0, 0.0));
246 /// if let Some((handle, hit)) = query_pipeline.cast_ray_and_get_normal(&ray, 100.0, true) {
247 /// println!("Hit at distance {}, surface normal: {:?}", hit.time_of_impact, hit.normal);
248 /// }
249 /// ```
250 #[profiling::function]
251 pub fn cast_ray_and_get_normal(
252 &self,
253 ray: &Ray,
254 max_toi: Real,
255 solid: bool,
256 ) -> Option<(ColliderHandle, RayIntersection)> {
257 CompositeShapeRef(self)
258 .cast_local_ray_and_get_normal(ray, max_toi, solid)
259 .and_then(|hit| self.id_to_handle(hit))
260 }
261
262 /// Returns ALL colliders that a ray passes through (not just the first).
263 ///
264 /// Unlike [`cast_ray()`](Self::cast_ray) which stops at the first hit, this returns
265 /// every collider along the ray's path. Useful for:
266 /// - Penetrating weapons that go through multiple objects
267 /// - Checking what's in a line (e.g., visibility through glass)
268 /// - Counting how many objects are between two points
269 ///
270 /// Returns an iterator of `(handle, collider, intersection)` tuples.
271 ///
272 /// # Example
273 /// ```
274 /// # use rapier3d::prelude::*;
275 /// # let mut bodies = RigidBodySet::new();
276 /// # let mut colliders = ColliderSet::new();
277 /// # let broad_phase = BroadPhaseBvh::new();
278 /// # let narrow_phase = NarrowPhase::new();
279 /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
280 /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
281 /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
282 /// # let ray = Ray::new(Vector::new(0.0, 10.0, 0.0), Vector::new(0.0, -1.0, 0.0));
283 /// for (handle, collider, hit) in query_pipeline.intersect_ray(ray, 100.0, true) {
284 /// println!("Ray passed through {:?} at distance {}", handle, hit.time_of_impact);
285 /// }
286 /// ```
287 #[profiling::function]
288 pub fn intersect_ray(
289 &'a self,
290 ray: Ray,
291 max_toi: Real,
292 solid: bool,
293 ) -> impl Iterator<Item = (ColliderHandle, &'a Collider, RayIntersection)> + 'a {
294 // TODO: add this to CompositeShapeRef?
295 self.bvh
296 .leaves(move |node: &BvhNode| node.aabb().intersects_local_ray(&ray, max_toi))
297 .filter_map(move |leaf| {
298 let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
299 if self.filter.test(self.bodies, co_handle, co) {
300 if let Some(intersection) =
301 co.shape
302 .cast_ray_and_get_normal(co.position(), &ray, max_toi, solid)
303 {
304 return Some((co_handle, co, intersection));
305 }
306 }
307
308 None
309 })
310 }
311
312 /// Finds the closest point on any collider to the given point.
313 ///
314 /// Returns the collider and information about where on its surface the closest point is.
315 /// Useful for:
316 /// - Finding nearest cover/obstacle
317 /// - Snap-to-surface mechanics
318 /// - Distance queries
319 ///
320 /// # Parameters
321 /// * `solid` - If `true`, a point inside a shape projects to itself. If `false`, it projects
322 /// to the nearest point on the shape's boundary
323 ///
324 /// # Example
325 /// ```
326 /// # use rapier3d::prelude::*;
327 /// # let params = IntegrationParameters::default();
328 /// # let mut bodies = RigidBodySet::new();
329 /// # let mut colliders = ColliderSet::new();
330 /// # let mut broad_phase = BroadPhaseBvh::new();
331 /// # let narrow_phase = NarrowPhase::new();
332 /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
333 /// # let ground_collider = ColliderBuilder::cuboid(10.0, 0.1, 10.0).build();
334 /// # let ground_aabb = ground_collider.compute_aabb();
335 /// # let collider_handle = colliders.insert_with_parent(ground_collider, ground, &mut bodies);
336 /// # broad_phase.set_aabb(¶ms, collider_handle, ground_aabb);
337 /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
338 /// let point = Vector::new(5.0, 0.0, 0.0);
339 /// if let Some((handle, projection)) = query_pipeline.project_point(point, std::f32::MAX, true) {
340 /// println!("Closest collider: {:?}", handle);
341 /// println!("Closest point: {:?}", projection.point);
342 /// println!("Distance: {}", (point - projection.point).length());
343 /// }
344 /// ```
345 #[profiling::function]
346 pub fn project_point(
347 &self,
348 point: Vector,
349 _max_dist: Real,
350 solid: bool,
351 ) -> Option<(ColliderHandle, PointProjection)> {
352 self.id_to_handle(CompositeShapeRef(self).project_local_point(point, solid))
353 }
354
355 /// Returns ALL colliders that contain the given point.
356 ///
357 /// A point is "inside" a collider if it's within its volume. Useful for:
358 /// - Detecting what area/trigger zones a point is in
359 /// - Checking if a position is inside geometry
360 /// - Finding all overlapping volumes at a location
361 ///
362 /// # Example
363 /// ```
364 /// # use rapier3d::prelude::*;
365 /// # let mut bodies = RigidBodySet::new();
366 /// # let mut colliders = ColliderSet::new();
367 /// # let broad_phase = BroadPhaseBvh::new();
368 /// # let narrow_phase = NarrowPhase::new();
369 /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
370 /// # colliders.insert_with_parent(ColliderBuilder::ball(5.0), ground, &mut bodies);
371 /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
372 /// let point = Vector::new(0.0, 0.0, 0.0);
373 /// for (handle, collider) in query_pipeline.intersect_point(point) {
374 /// println!("Point is inside {:?}", handle);
375 /// }
376 /// ```
377 #[profiling::function]
378 pub fn intersect_point(
379 &'a self,
380 point: Vector,
381 ) -> impl Iterator<Item = (ColliderHandle, &'a Collider)> + 'a {
382 // TODO: add to CompositeShapeRef?
383 self.bvh
384 .leaves(move |node: &BvhNode| node.aabb().contains_local_point(point))
385 .filter_map(move |leaf| {
386 let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
387 if self.filter.test(self.bodies, co_handle, co)
388 && co.shape.contains_point(co.position(), point)
389 {
390 return Some((co_handle, co));
391 }
392
393 None
394 })
395 }
396
397 /// Find the projection of a point on the closest collider.
398 ///
399 /// The results include the ID of the feature hit by the point.
400 ///
401 /// # Parameters
402 /// * `point` - The point to project.
403 #[profiling::function]
404 pub fn project_point_and_get_feature(
405 &self,
406 point: Vector,
407 ) -> Option<(ColliderHandle, PointProjection, FeatureId)> {
408 let (id, (proj, feat)) = CompositeShapeRef(self).project_local_point_and_get_feature(point);
409 let handle = self.colliders.get_unknown_gen(id)?.1;
410 Some((handle, proj, feat))
411 }
412
413 /// Finds all handles of all the colliders with an [`Aabb`] intersecting the given [`Aabb`].
414 ///
415 /// Note that the collider AABB taken into account is the one currently stored in the query
416 /// pipeline’s BVH. It doesn’t recompute the latest collider AABB.
417 #[profiling::function]
418 pub fn intersect_aabb_conservative(
419 &'a self,
420 aabb: Aabb,
421 ) -> impl Iterator<Item = (ColliderHandle, &'a Collider)> + 'a {
422 // TODO: add to ColliderRef?
423 self.bvh
424 .leaves(move |node: &BvhNode| node.aabb().intersects(&aabb))
425 .filter_map(move |leaf| {
426 let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
427 // NOTE: do **not** recompute and check the latest collider AABB.
428 // Checking only against the one in the BVH is useful, e.g., for conservative
429 // scene queries for CCD.
430 if self.filter.test(self.bodies, co_handle, co) {
431 return Some((co_handle, co));
432 }
433
434 None
435 })
436 }
437
438 /// Sweeps a shape through the world to find what it would collide with.
439 ///
440 /// Like raycasting, but instead of a thin ray, you're moving an entire shape (sphere, box, etc.)
441 /// through space. This is also called "shape casting" or "sweep testing". Useful for:
442 /// - Predicting where a moving object will hit something
443 /// - Checking if a movement is valid before executing it
444 /// - Thick raycasts (e.g., character controller collision prediction)
445 /// - Area-of-effect scanning along a path
446 ///
447 /// Returns the first collision: `(collider_handle, hit_details)` where hit contains
448 /// time-of-impact, witness points, and surface normal.
449 ///
450 /// # Parameters
451 /// * `shape_pos` - Starting position/orientation of the shape
452 /// * `shape_vel` - Direction and speed to move the shape (velocity vector)
453 /// * `shape` - The shape to sweep (ball, cuboid, capsule, etc.)
454 /// * `options` - Maximum distance, collision filtering, etc.
455 ///
456 /// # Example
457 /// ```
458 /// # use rapier3d::prelude::*;
459 /// # use rapier3d::parry::{query::ShapeCastOptions, shape::Ball};
460 /// # let mut bodies = RigidBodySet::new();
461 /// # let mut colliders = ColliderSet::new();
462 /// # let narrow_phase = NarrowPhase::new();
463 /// # let broad_phase = BroadPhaseBvh::new();
464 /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
465 /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
466 /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
467 /// // Sweep a sphere downward
468 /// let shape = Ball::new(0.5);
469 /// let start_pos = Pose::translation(0.0, 10.0, 0.0);
470 /// let velocity = Vector::new(0.0, -1.0, 0.0);
471 /// let options = ShapeCastOptions::default();
472 ///
473 /// if let Some((handle, hit)) = query_pipeline.cast_shape(&start_pos, velocity, &shape, options) {
474 /// println!("Shape would hit {:?} at time {}", handle, hit.time_of_impact);
475 /// }
476 /// ```
477 #[profiling::function]
478 pub fn cast_shape(
479 &self,
480 shape_pos: &Pose,
481 shape_vel: Vector,
482 shape: &dyn Shape,
483 options: ShapeCastOptions,
484 ) -> Option<(ColliderHandle, ShapeCastHit)> {
485 CompositeShapeRef(self)
486 .cast_shape(self.dispatcher, shape_pos, shape_vel, shape, options)
487 .and_then(|hit| self.id_to_handle(hit))
488 }
489
490 /// Casts a shape with an arbitrary continuous motion and retrieve the first collider it hits.
491 ///
492 /// In the resulting `TOI`, witness and normal 1 refer to the world collider, and are in world
493 /// space.
494 ///
495 /// # Parameters
496 /// * `shape_motion` - The motion of the shape.
497 /// * `shape` - The shape to cast.
498 /// * `start_time` - The starting time of the interval where the motion takes place.
499 /// * `end_time` - The end time of the interval where the motion takes place.
500 /// * `stop_at_penetration` - If the casted shape starts in a penetration state with any
501 /// collider, two results are possible. If `stop_at_penetration` is `true` then, the
502 /// result will have a `toi` equal to `start_time`. If `stop_at_penetration` is `false`
503 /// then the nonlinear shape-casting will see if further motion with respect to the penetration normal
504 /// would result in tunnelling. If it does not (i.e. we have a separating velocity along
505 /// that normal) then the nonlinear shape-casting will attempt to find another impact,
506 /// at a time `> start_time` that could result in tunnelling.
507 #[profiling::function]
508 pub fn cast_shape_nonlinear(
509 &self,
510 shape_motion: &NonlinearRigidMotion,
511 shape: &dyn Shape,
512 start_time: Real,
513 end_time: Real,
514 stop_at_penetration: bool,
515 ) -> Option<(ColliderHandle, ShapeCastHit)> {
516 CompositeShapeRef(self)
517 .cast_shape_nonlinear(
518 self.dispatcher,
519 &NonlinearRigidMotion::identity(),
520 shape_motion,
521 shape,
522 start_time,
523 end_time,
524 stop_at_penetration,
525 )
526 .and_then(|hit| self.id_to_handle(hit))
527 }
528
529 /// Retrieve all the colliders intersecting the given shape.
530 ///
531 /// # Parameters
532 /// * `shapePos` - The pose of the shape to test.
533 /// * `shape` - The shape to test.
534 #[profiling::function]
535 pub fn intersect_shape(
536 &'a self,
537 shape_pos: Pose,
538 shape: &'a dyn Shape,
539 ) -> impl Iterator<Item = (ColliderHandle, &'a Collider)> + 'a {
540 // TODO: add this to CompositeShapeRef?
541 let shape_aabb = shape.compute_aabb(&shape_pos);
542 self.bvh
543 .leaves(move |node: &BvhNode| node.aabb().intersects(&shape_aabb))
544 .filter_map(move |leaf| {
545 let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
546 if self.filter.test(self.bodies, co_handle, co) {
547 let pos12 = shape_pos.inv_mul(co.position());
548 if self.dispatcher.intersection_test(&pos12, shape, co.shape()) == Ok(true) {
549 return Some((co_handle, co));
550 }
551 }
552
553 None
554 })
555 }
556}
557
558bitflags::bitflags! {
559 #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
560 /// Flags for filtering spatial queries by body type or sensor status.
561 ///
562 /// Use these to quickly exclude categories of colliders from raycasts and other queries.
563 ///
564 /// # Example
565 /// ```
566 /// # use rapier3d::prelude::*;
567 /// // Raycast that only hits dynamic objects (ignore walls/floors)
568 /// let filter = QueryFilter::from(QueryFilterFlags::ONLY_DYNAMIC);
569 ///
570 /// // Find only trigger zones, not solid geometry
571 /// let filter = QueryFilter::from(QueryFilterFlags::EXCLUDE_SOLIDS);
572 /// ```
573 pub struct QueryFilterFlags: u32 {
574 /// Excludes fixed bodies and standalone colliders.
575 const EXCLUDE_FIXED = 1 << 0;
576 /// Excludes kinematic bodies.
577 const EXCLUDE_KINEMATIC = 1 << 1;
578 /// Excludes dynamic bodies.
579 const EXCLUDE_DYNAMIC = 1 << 2;
580 /// Excludes sensors (trigger zones).
581 const EXCLUDE_SENSORS = 1 << 3;
582 /// Excludes solid colliders (only hit sensors).
583 const EXCLUDE_SOLIDS = 1 << 4;
584 /// Only includes dynamic bodies.
585 const ONLY_DYNAMIC = Self::EXCLUDE_FIXED.bits() | Self::EXCLUDE_KINEMATIC.bits();
586 /// Only includes kinematic bodies.
587 const ONLY_KINEMATIC = Self::EXCLUDE_DYNAMIC.bits() | Self::EXCLUDE_FIXED.bits();
588 /// Only includes fixed bodies (excluding standalone colliders).
589 const ONLY_FIXED = Self::EXCLUDE_DYNAMIC.bits() | Self::EXCLUDE_KINEMATIC.bits();
590 }
591}
592
593impl QueryFilterFlags {
594 /// Tests if the given collider should be taken into account by a scene query, based
595 /// on the flags on `self`.
596 #[inline]
597 pub fn test(&self, bodies: &RigidBodySet, collider: &Collider) -> bool {
598 if self.is_empty() {
599 // No filter.
600 return true;
601 }
602
603 if (self.contains(QueryFilterFlags::EXCLUDE_SENSORS) && collider.is_sensor())
604 || (self.contains(QueryFilterFlags::EXCLUDE_SOLIDS) && !collider.is_sensor())
605 {
606 return false;
607 }
608
609 if self.contains(QueryFilterFlags::EXCLUDE_FIXED) && collider.parent.is_none() {
610 return false;
611 }
612
613 if let Some(parent) = collider.parent.and_then(|p| bodies.get(p.handle)) {
614 let parent_type = parent.body_type();
615
616 if (self.contains(QueryFilterFlags::EXCLUDE_FIXED) && parent_type.is_fixed())
617 || (self.contains(QueryFilterFlags::EXCLUDE_KINEMATIC)
618 && parent_type.is_kinematic())
619 || (self.contains(QueryFilterFlags::EXCLUDE_DYNAMIC) && parent_type.is_dynamic())
620 {
621 return false;
622 }
623 }
624
625 true
626 }
627}
628
629/// Filtering rules for spatial queries (raycasts, shape casts, etc.).
630///
631/// Controls which colliders should be included/excluded from query results.
632/// By default, all colliders are included.
633///
634/// # Common filters
635///
636/// ```
637/// # use rapier3d::prelude::*;
638/// # let player_collider = ColliderHandle::from_raw_parts(0, 0);
639/// # let enemy_groups = InteractionGroups::all();
640/// // Only hit dynamic objects (ignore static walls)
641/// let filter = QueryFilter::only_dynamic();
642///
643/// // Hit everything except the player's own collider
644/// let filter = QueryFilter::default()
645/// .exclude_collider(player_collider);
646///
647/// // Raycast that only hits enemies (using collision groups)
648/// let filter = QueryFilter::default()
649/// .groups(enemy_groups);
650///
651/// // Custom filtering with a closure
652/// let filter = QueryFilter::default()
653/// .predicate(&|handle, collider| {
654/// // Only hit colliders with user_data > 100
655/// collider.user_data > 100
656/// });
657/// ```
658#[derive(Copy, Clone, Default)]
659pub struct QueryFilter<'a> {
660 /// Flags for excluding fixed/kinematic/dynamic bodies or sensors/solids.
661 pub flags: QueryFilterFlags,
662 /// If set, only colliders with compatible collision groups are included.
663 pub groups: Option<InteractionGroups>,
664 /// If set, this specific collider is excluded.
665 pub exclude_collider: Option<ColliderHandle>,
666 /// If set, all colliders attached to this body are excluded.
667 pub exclude_rigid_body: Option<RigidBodyHandle>,
668 /// Custom filtering function - collider included only if this returns `true`.
669 #[allow(clippy::type_complexity)]
670 pub predicate: Option<&'a dyn Fn(ColliderHandle, &Collider) -> bool>,
671}
672
673impl QueryFilter<'_> {
674 /// Applies the filters described by `self` to a collider to determine if it has to be
675 /// included in a scene query (`true`) or not (`false`).
676 #[inline]
677 pub fn test(&self, bodies: &RigidBodySet, handle: ColliderHandle, collider: &Collider) -> bool {
678 self.exclude_collider != Some(handle)
679 && (self.exclude_rigid_body.is_none() // NOTE: deal with the `None` case separately otherwise the next test is incorrect if the collider’s parent is `None` too.
680 || self.exclude_rigid_body != collider.parent.map(|p| p.handle))
681 && self
682 .groups
683 .map(|grps| collider.flags.collision_groups.test(grps))
684 .unwrap_or(true)
685 && self.flags.test(bodies, collider)
686 && self.predicate.map(|f| f(handle, collider)).unwrap_or(true)
687 }
688}
689
690impl From<QueryFilterFlags> for QueryFilter<'_> {
691 fn from(flags: QueryFilterFlags) -> Self {
692 Self {
693 flags,
694 ..QueryFilter::default()
695 }
696 }
697}
698
699impl From<InteractionGroups> for QueryFilter<'_> {
700 fn from(groups: InteractionGroups) -> Self {
701 Self {
702 groups: Some(groups),
703 ..QueryFilter::default()
704 }
705 }
706}
707
708impl<'a> QueryFilter<'a> {
709 /// A query filter that doesn’t exclude any collider.
710 pub fn new() -> Self {
711 Self::default()
712 }
713
714 /// Exclude from the query any collider attached to a fixed rigid-body and colliders with no rigid-body attached.
715 pub fn exclude_fixed() -> Self {
716 QueryFilterFlags::EXCLUDE_FIXED.into()
717 }
718
719 /// Exclude from the query any collider attached to a kinematic rigid-body.
720 pub fn exclude_kinematic() -> Self {
721 QueryFilterFlags::EXCLUDE_KINEMATIC.into()
722 }
723
724 /// Exclude from the query any collider attached to a dynamic rigid-body.
725 pub fn exclude_dynamic() -> Self {
726 QueryFilterFlags::EXCLUDE_DYNAMIC.into()
727 }
728
729 /// Excludes all colliders not attached to a dynamic rigid-body.
730 pub fn only_dynamic() -> Self {
731 QueryFilterFlags::ONLY_DYNAMIC.into()
732 }
733
734 /// Excludes all colliders not attached to a kinematic rigid-body.
735 pub fn only_kinematic() -> Self {
736 QueryFilterFlags::ONLY_KINEMATIC.into()
737 }
738
739 /// Exclude all colliders attached to a non-fixed rigid-body
740 /// (this will not exclude colliders not attached to any rigid-body).
741 pub fn only_fixed() -> Self {
742 QueryFilterFlags::ONLY_FIXED.into()
743 }
744
745 /// Exclude from the query any collider that is a sensor.
746 pub fn exclude_sensors(mut self) -> Self {
747 self.flags |= QueryFilterFlags::EXCLUDE_SENSORS;
748 self
749 }
750
751 /// Exclude from the query any collider that is not a sensor.
752 pub fn exclude_solids(mut self) -> Self {
753 self.flags |= QueryFilterFlags::EXCLUDE_SOLIDS;
754 self
755 }
756
757 /// Only colliders with collision groups compatible with this one will
758 /// be included in the scene query.
759 pub fn groups(mut self, groups: InteractionGroups) -> Self {
760 self.groups = Some(groups);
761 self
762 }
763
764 /// Set the collider that will be excluded from the scene query.
765 pub fn exclude_collider(mut self, collider: ColliderHandle) -> Self {
766 self.exclude_collider = Some(collider);
767 self
768 }
769
770 /// Set the rigid-body that will be excluded from the scene query.
771 pub fn exclude_rigid_body(mut self, rigid_body: RigidBodyHandle) -> Self {
772 self.exclude_rigid_body = Some(rigid_body);
773 self
774 }
775
776 /// Set the predicate to apply a custom collider filtering during the scene query.
777 pub fn predicate(mut self, predicate: &'a impl Fn(ColliderHandle, &Collider) -> bool) -> Self {
778 self.predicate = Some(predicate);
779 self
780 }
781}