Skip to main content

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(&params, 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}