Skip to main content

rapier3d/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, max_dist, 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        max_dist: Real,
408    ) -> Option<(ColliderHandle, PointProjection, FeatureId)> {
409        let (id, (proj, feat)) =
410            CompositeShapeRef(self).project_local_point_and_get_feature(point, max_dist)?;
411        let handle = self.colliders.get_unknown_gen(id)?.1;
412        Some((handle, proj, feat))
413    }
414
415    /// Finds all handles of all the colliders with an [`Aabb`] intersecting the given [`Aabb`].
416    ///
417    /// Note that the collider AABB taken into account is the one currently stored in the query
418    /// pipeline’s BVH. It doesn’t recompute the latest collider AABB.
419    #[profiling::function]
420    pub fn intersect_aabb_conservative(
421        &'a self,
422        aabb: Aabb,
423    ) -> impl Iterator<Item = (ColliderHandle, &'a Collider)> + 'a {
424        // TODO: add to ColliderRef?
425        self.bvh
426            .leaves(move |node: &BvhNode| node.aabb().intersects(&aabb))
427            .filter_map(move |leaf| {
428                let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
429                // NOTE: do **not** recompute and check the latest collider AABB.
430                //       Checking only against the one in the BVH is useful, e.g., for conservative
431                //       scene queries for CCD.
432                if self.filter.test(self.bodies, co_handle, co) {
433                    return Some((co_handle, co));
434                }
435
436                None
437            })
438    }
439
440    /// Sweeps a shape through the world to find what it would collide with.
441    ///
442    /// Like raycasting, but instead of a thin ray, you're moving an entire shape (sphere, box, etc.)
443    /// through space. This is also called "shape casting" or "sweep testing". Useful for:
444    /// - Predicting where a moving object will hit something
445    /// - Checking if a movement is valid before executing it
446    /// - Thick raycasts (e.g., character controller collision prediction)
447    /// - Area-of-effect scanning along a path
448    ///
449    /// Returns the first collision: `(collider_handle, hit_details)` where hit contains
450    /// time-of-impact, witness points, and surface normal.
451    ///
452    /// In the returned [`ShapeCastHit`], `witness1` and `normal1` refer to the hit collider
453    /// and are expressed in **world space**. `witness2` and `normal2` refer to the cast shape
454    /// and are expressed in its **local space** (relative to `shape_pos`).
455    ///
456    /// # Parameters
457    /// * `shape_pos` - Starting position/orientation of the shape
458    /// * `shape_vel` - Direction and speed to move the shape (velocity vector)
459    /// * `shape` - The shape to sweep (ball, cuboid, capsule, etc.)
460    /// * `options` - Maximum distance, collision filtering, etc.
461    ///
462    /// # Example
463    /// ```
464    /// # use rapier3d::prelude::*;
465    /// # use rapier3d::parry::{query::ShapeCastOptions, shape::Ball};
466    /// # let mut bodies = RigidBodySet::new();
467    /// # let mut colliders = ColliderSet::new();
468    /// # let narrow_phase = NarrowPhase::new();
469    /// # let broad_phase = BroadPhaseBvh::new();
470    /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
471    /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
472    /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
473    /// // Sweep a sphere downward
474    /// let shape = Ball::new(0.5);
475    /// let start_pos = Pose::translation(0.0, 10.0, 0.0);
476    /// let velocity = Vector::new(0.0, -1.0, 0.0);
477    /// let options = ShapeCastOptions::default();
478    ///
479    /// if let Some((handle, hit)) = query_pipeline.cast_shape(&start_pos, velocity, &shape, options) {
480    ///     println!("Shape would hit {:?} at time {}", handle, hit.time_of_impact);
481    /// }
482    /// ```
483    #[profiling::function]
484    pub fn cast_shape(
485        &self,
486        shape_pos: &Pose,
487        shape_vel: Vector,
488        shape: &dyn Shape,
489        options: ShapeCastOptions,
490    ) -> Option<(ColliderHandle, ShapeCastHit)> {
491        CompositeShapeRef(self)
492            .cast_shape(self.dispatcher, shape_pos, shape_vel, shape, options)
493            .and_then(|hit| self.id_to_handle(hit))
494    }
495
496    /// Casts a shape with an arbitrary continuous motion and retrieve the first collider it hits.
497    ///
498    /// In the returned [`ShapeCastHit`], `witness1` and `normal1` refer to the hit collider
499    /// and are expressed in **world space**. `witness2` and `normal2` refer to the cast shape
500    /// and are expressed in its **local space** (they follow the shape along `shape_motion`).
501    ///
502    /// # Parameters
503    /// * `shape_motion` - The motion of the shape.
504    /// * `shape` - The shape to cast.
505    /// * `start_time` - The starting time of the interval where the motion takes place.
506    /// * `end_time` - The end time of the interval where the motion takes place.
507    /// * `stop_at_penetration` - If the casted shape starts in a penetration state with any
508    ///    collider, two results are possible. If `stop_at_penetration` is `true` then, the
509    ///    result will have a `toi` equal to `start_time`. If `stop_at_penetration` is `false`
510    ///    then the nonlinear shape-casting will see if further motion with respect to the penetration normal
511    ///    would result in tunnelling. If it does not (i.e. we have a separating velocity along
512    ///    that normal) then the nonlinear shape-casting will attempt to find another impact,
513    ///    at a time `> start_time` that could result in tunnelling.
514    #[profiling::function]
515    pub fn cast_shape_nonlinear(
516        &self,
517        shape_motion: &NonlinearRigidMotion,
518        shape: &dyn Shape,
519        start_time: Real,
520        end_time: Real,
521        stop_at_penetration: bool,
522    ) -> Option<(ColliderHandle, ShapeCastHit)> {
523        CompositeShapeRef(self)
524            .cast_shape_nonlinear(
525                self.dispatcher,
526                &NonlinearRigidMotion::identity(),
527                shape_motion,
528                shape,
529                start_time,
530                end_time,
531                stop_at_penetration,
532            )
533            .and_then(|hit| self.id_to_handle(hit))
534    }
535
536    /// Retrieve all the colliders intersecting the given shape.
537    ///
538    /// # Parameters
539    /// * `shapePos` - The pose of the shape to test.
540    /// * `shape` - The shape to test.
541    #[profiling::function]
542    pub fn intersect_shape(
543        &'a self,
544        shape_pos: Pose,
545        shape: &'a dyn Shape,
546    ) -> impl Iterator<Item = (ColliderHandle, &'a Collider)> + 'a {
547        // TODO: add this to CompositeShapeRef?
548        let shape_aabb = shape.compute_aabb(&shape_pos);
549        self.bvh
550            .leaves(move |node: &BvhNode| node.aabb().intersects(&shape_aabb))
551            .filter_map(move |leaf| {
552                let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
553                if self.filter.test(self.bodies, co_handle, co) {
554                    let pos12 = shape_pos.inv_mul(co.position());
555                    if self.dispatcher.intersection_test(&pos12, shape, co.shape()) == Ok(true) {
556                        return Some((co_handle, co));
557                    }
558                }
559
560                None
561            })
562    }
563}
564
565bitflags::bitflags! {
566    #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
567    /// Flags for filtering spatial queries by body type or sensor status.
568    ///
569    /// Use these to quickly exclude categories of colliders from raycasts and other queries.
570    ///
571    /// # Example
572    /// ```
573    /// # use rapier3d::prelude::*;
574    /// // Raycast that only hits dynamic objects (ignore walls/floors)
575    /// let filter = QueryFilter::from(QueryFilterFlags::ONLY_DYNAMIC);
576    ///
577    /// // Find only trigger zones, not solid geometry
578    /// let filter = QueryFilter::from(QueryFilterFlags::EXCLUDE_SOLIDS);
579    /// ```
580    pub struct QueryFilterFlags: u32 {
581        /// Excludes fixed bodies and standalone colliders.
582        const EXCLUDE_FIXED = 1 << 0;
583        /// Excludes kinematic bodies.
584        const EXCLUDE_KINEMATIC = 1 << 1;
585        /// Excludes dynamic bodies.
586        const EXCLUDE_DYNAMIC = 1 << 2;
587        /// Excludes sensors (trigger zones).
588        const EXCLUDE_SENSORS = 1 << 3;
589        /// Excludes solid colliders (only hit sensors).
590        const EXCLUDE_SOLIDS = 1 << 4;
591        /// Only includes dynamic bodies.
592        const ONLY_DYNAMIC = Self::EXCLUDE_FIXED.bits() | Self::EXCLUDE_KINEMATIC.bits();
593        /// Only includes kinematic bodies.
594        const ONLY_KINEMATIC = Self::EXCLUDE_DYNAMIC.bits() | Self::EXCLUDE_FIXED.bits();
595        /// Only includes fixed bodies (excluding standalone colliders).
596        const ONLY_FIXED = Self::EXCLUDE_DYNAMIC.bits() | Self::EXCLUDE_KINEMATIC.bits();
597    }
598}
599
600impl QueryFilterFlags {
601    /// Tests if the given collider should be taken into account by a scene query, based
602    /// on the flags on `self`.
603    #[inline]
604    pub fn test(&self, bodies: &RigidBodySet, collider: &Collider) -> bool {
605        if self.is_empty() {
606            // No filter.
607            return true;
608        }
609
610        if (self.contains(QueryFilterFlags::EXCLUDE_SENSORS) && collider.is_sensor())
611            || (self.contains(QueryFilterFlags::EXCLUDE_SOLIDS) && !collider.is_sensor())
612        {
613            return false;
614        }
615
616        if self.contains(QueryFilterFlags::EXCLUDE_FIXED) && collider.parent.is_none() {
617            return false;
618        }
619
620        if let Some(parent) = collider.parent.and_then(|p| bodies.get(p.handle)) {
621            let parent_type = parent.body_type();
622
623            if (self.contains(QueryFilterFlags::EXCLUDE_FIXED) && parent_type.is_fixed())
624                || (self.contains(QueryFilterFlags::EXCLUDE_KINEMATIC)
625                    && parent_type.is_kinematic())
626                || (self.contains(QueryFilterFlags::EXCLUDE_DYNAMIC) && parent_type.is_dynamic())
627            {
628                return false;
629            }
630        }
631
632        true
633    }
634}
635
636/// Filtering rules for spatial queries (raycasts, shape casts, etc.).
637///
638/// Controls which colliders should be included/excluded from query results.
639/// By default, all colliders are included.
640///
641/// # Common filters
642///
643/// ```
644/// # use rapier3d::prelude::*;
645/// # let player_collider = ColliderHandle::from_raw_parts(0, 0);
646/// # let enemy_groups = InteractionGroups::all();
647/// // Only hit dynamic objects (ignore static walls)
648/// let filter = QueryFilter::only_dynamic();
649///
650/// // Hit everything except the player's own collider
651/// let filter = QueryFilter::default()
652///     .exclude_collider(player_collider);
653///
654/// // Raycast that only hits enemies (using collision groups)
655/// let filter = QueryFilter::default()
656///     .groups(enemy_groups);
657///
658/// // Custom filtering with a closure
659/// let filter = QueryFilter::default()
660///     .predicate(&|handle, collider| {
661///         // Only hit colliders with user_data > 100
662///         collider.user_data > 100
663///     });
664/// ```
665#[derive(Copy, Clone, Default)]
666pub struct QueryFilter<'a> {
667    /// Flags for excluding fixed/kinematic/dynamic bodies or sensors/solids.
668    pub flags: QueryFilterFlags,
669    /// If set, only colliders with compatible collision groups are included.
670    pub groups: Option<InteractionGroups>,
671    /// If set, this specific collider is excluded.
672    pub exclude_collider: Option<ColliderHandle>,
673    /// If set, all colliders attached to this body are excluded.
674    pub exclude_rigid_body: Option<RigidBodyHandle>,
675    /// Custom filtering function - collider included only if this returns `true`.
676    #[allow(clippy::type_complexity)]
677    pub predicate: Option<&'a dyn Fn(ColliderHandle, &Collider) -> bool>,
678}
679
680impl QueryFilter<'_> {
681    /// Applies the filters described by `self` to a collider to determine if it has to be
682    /// included in a scene query (`true`) or not (`false`).
683    #[inline]
684    pub fn test(&self, bodies: &RigidBodySet, handle: ColliderHandle, collider: &Collider) -> bool {
685        self.exclude_collider != Some(handle)
686            && (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.
687            || self.exclude_rigid_body != collider.parent.map(|p| p.handle))
688            && self
689                .groups
690                .map(|grps| collider.flags.collision_groups.test(grps))
691                .unwrap_or(true)
692            && self.flags.test(bodies, collider)
693            && self.predicate.map(|f| f(handle, collider)).unwrap_or(true)
694    }
695}
696
697impl From<QueryFilterFlags> for QueryFilter<'_> {
698    fn from(flags: QueryFilterFlags) -> Self {
699        Self {
700            flags,
701            ..QueryFilter::default()
702        }
703    }
704}
705
706impl From<InteractionGroups> for QueryFilter<'_> {
707    fn from(groups: InteractionGroups) -> Self {
708        Self {
709            groups: Some(groups),
710            ..QueryFilter::default()
711        }
712    }
713}
714
715impl<'a> QueryFilter<'a> {
716    /// A query filter that doesn’t exclude any collider.
717    pub fn new() -> Self {
718        Self::default()
719    }
720
721    /// Exclude from the query any collider attached to a fixed rigid-body and colliders with no rigid-body attached.
722    pub fn exclude_fixed() -> Self {
723        QueryFilterFlags::EXCLUDE_FIXED.into()
724    }
725
726    /// Exclude from the query any collider attached to a kinematic rigid-body.
727    pub fn exclude_kinematic() -> Self {
728        QueryFilterFlags::EXCLUDE_KINEMATIC.into()
729    }
730
731    /// Exclude from the query any collider attached to a dynamic rigid-body.
732    pub fn exclude_dynamic() -> Self {
733        QueryFilterFlags::EXCLUDE_DYNAMIC.into()
734    }
735
736    /// Excludes all colliders not attached to a dynamic rigid-body.
737    pub fn only_dynamic() -> Self {
738        QueryFilterFlags::ONLY_DYNAMIC.into()
739    }
740
741    /// Excludes all colliders not attached to a kinematic rigid-body.
742    pub fn only_kinematic() -> Self {
743        QueryFilterFlags::ONLY_KINEMATIC.into()
744    }
745
746    /// Exclude all colliders attached to a non-fixed rigid-body
747    /// (this will not exclude colliders not attached to any rigid-body).
748    pub fn only_fixed() -> Self {
749        QueryFilterFlags::ONLY_FIXED.into()
750    }
751
752    /// Exclude from the query any collider that is a sensor.
753    pub fn exclude_sensors(mut self) -> Self {
754        self.flags |= QueryFilterFlags::EXCLUDE_SENSORS;
755        self
756    }
757
758    /// Exclude from the query any collider that is not a sensor.
759    pub fn exclude_solids(mut self) -> Self {
760        self.flags |= QueryFilterFlags::EXCLUDE_SOLIDS;
761        self
762    }
763
764    /// Only colliders with collision groups compatible with this one will
765    /// be included in the scene query.
766    pub fn groups(mut self, groups: InteractionGroups) -> Self {
767        self.groups = Some(groups);
768        self
769    }
770
771    /// Set the collider that will be excluded from the scene query.
772    pub fn exclude_collider(mut self, collider: ColliderHandle) -> Self {
773        self.exclude_collider = Some(collider);
774        self
775    }
776
777    /// Set the rigid-body that will be excluded from the scene query.
778    pub fn exclude_rigid_body(mut self, rigid_body: RigidBodyHandle) -> Self {
779        self.exclude_rigid_body = Some(rigid_body);
780        self
781    }
782
783    /// Set the predicate to apply a custom collider filtering during the scene query.
784    pub fn predicate(mut self, predicate: &'a impl Fn(ColliderHandle, &Collider) -> bool) -> Self {
785        self.predicate = Some(predicate);
786        self
787    }
788}
789
790#[cfg(test)]
791mod test {
792    use super::QueryFilter;
793    use crate::dynamics::{
794        CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet,
795        RigidBodyBuilder, RigidBodySet,
796    };
797    use crate::geometry::{BroadPhaseBvh, ColliderBuilder, ColliderSet, NarrowPhase};
798    use crate::math::{Real, Vector};
799    use crate::pipeline::PhysicsPipeline;
800
801    /// Steps the pipeline once so that `broad_phase` ends up with an up-to-date BVH.
802    fn step(
803        bodies: &mut RigidBodySet,
804        colliders: &mut ColliderSet,
805        broad_phase: &mut BroadPhaseBvh,
806        narrow_phase: &mut NarrowPhase,
807    ) {
808        PhysicsPipeline::new().step(
809            Vector::ZERO,
810            &IntegrationParameters::default(),
811            &mut IslandManager::new(),
812            broad_phase,
813            narrow_phase,
814            bodies,
815            colliders,
816            &mut ImpulseJointSet::new(),
817            &mut MultibodyJointSet::new(),
818            &mut CCDSolver::new(),
819            &(),
820            &(),
821        );
822    }
823
824    /// Regression test for issues #923 and #926: projecting a point when there is no
825    /// collider to project onto must return `None` instead of panicking.
826    #[test]
827    fn project_point_on_empty_pipeline_returns_none() {
828        let mut bodies = RigidBodySet::new();
829        let mut colliders = ColliderSet::new();
830        let mut broad_phase = BroadPhaseBvh::new();
831        let mut narrow_phase = NarrowPhase::new();
832        step(
833            &mut bodies,
834            &mut colliders,
835            &mut broad_phase,
836            &mut narrow_phase,
837        );
838
839        let qp = broad_phase.as_query_pipeline(
840            narrow_phase.query_dispatcher(),
841            &bodies,
842            &colliders,
843            QueryFilter::default(),
844        );
845        assert!(qp.project_point(Vector::ZERO, Real::MAX, true).is_none());
846        assert!(
847            qp.project_point_and_get_feature(Vector::ZERO, Real::MAX)
848                .is_none()
849        );
850    }
851
852    /// Regression test for issue #923: when every collider is removed by the query
853    /// filter, `project_point` must return `None` instead of panicking.
854    #[test]
855    fn project_point_with_all_colliders_filtered_returns_none() {
856        let mut bodies = RigidBodySet::new();
857        let mut colliders = ColliderSet::new();
858        let mut broad_phase = BroadPhaseBvh::new();
859        let mut narrow_phase = NarrowPhase::new();
860
861        // A standalone (non-dynamic) collider.
862        colliders.insert(ColliderBuilder::ball(1.0));
863        step(
864            &mut bodies,
865            &mut colliders,
866            &mut broad_phase,
867            &mut narrow_phase,
868        );
869
870        // `only_dynamic` excludes the standalone collider, leaving nothing to project onto.
871        let qp = broad_phase.as_query_pipeline(
872            narrow_phase.query_dispatcher(),
873            &bodies,
874            &colliders,
875            QueryFilter::only_dynamic(),
876        );
877        assert!(qp.project_point(Vector::ZERO, Real::MAX, true).is_none());
878        assert!(
879            qp.project_point_and_get_feature(Vector::ZERO, Real::MAX)
880                .is_none()
881        );
882    }
883
884    /// Regression test for issue #921: `project_point` must honor its `max_dist` parameter.
885    #[test]
886    fn project_point_respects_max_dist() {
887        let mut bodies = RigidBodySet::new();
888        let mut colliders = ColliderSet::new();
889        let mut broad_phase = BroadPhaseBvh::new();
890        let mut narrow_phase = NarrowPhase::new();
891
892        // A unit ball at the origin.
893        let body = bodies.insert(RigidBodyBuilder::fixed());
894        let handle = colliders.insert_with_parent(ColliderBuilder::ball(1.0), body, &mut bodies);
895        step(
896            &mut bodies,
897            &mut colliders,
898            &mut broad_phase,
899            &mut narrow_phase,
900        );
901
902        let qp = broad_phase.as_query_pipeline(
903            narrow_phase.query_dispatcher(),
904            &bodies,
905            &colliders,
906            QueryFilter::default(),
907        );
908
909        // Point 10 units away along X: its closest point on the unit ball is 9 units away.
910        let point = Vector::X * 10.0;
911
912        // `max_dist` smaller than the actual distance => no result.
913        assert!(qp.project_point(point, 1.0, false).is_none());
914
915        // `max_dist` larger than the actual distance => the ball is found...
916        let (found, proj) = qp.project_point(point, 100.0, false).unwrap();
917        assert_eq!(found, handle);
918        // ...and the projection lies within `max_dist` of the queried point.
919        assert!((proj.point - point).length() <= 100.0);
920    }
921}