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