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    /// The handle of the collider parry reported as the part of the collider set that answered.
175    fn handle_of(&self, id: u32) -> Option<ColliderHandle> {
176        self.colliders.get_unknown_gen(id).map(|(_, h)| h)
177    }
178
179    /// Replaces [`Self::filter`] with different filtering rules.
180    pub fn with_filter(self, filter: QueryFilter<'a>) -> Self {
181        Self { filter, ..self }
182    }
183
184    /// Casts a ray through the world and returns the first collider it hits.
185    ///
186    /// This is one of the most common operations - use it for line-of-sight checks,
187    /// projectile trajectories, mouse picking, laser beams, etc.
188    ///
189    /// Returns `Some((handle, distance))` if the ray hits something, where:
190    /// - `handle` is which collider was hit
191    /// - `distance` is how far along the ray the hit occurred (time-of-impact)
192    ///
193    /// # Parameters
194    /// * `ray` - The ray to cast (origin + direction). Create with `Ray::new(origin, direction)`
195    /// * `max_toi` - Maximum distance to check. Use `Real::MAX` for unlimited range
196    /// * `solid` - If `true`, detects hits even if the ray starts inside a shape. If `false`,
197    ///   the ray "passes through" from the inside until it exits
198    ///
199    /// # Example
200    /// ```
201    /// # use rapier3d::prelude::*;
202    /// # let mut bodies = RigidBodySet::new();
203    /// # let mut colliders = ColliderSet::new();
204    /// # let broad_phase = BroadPhaseBvh::new();
205    /// # let narrow_phase = NarrowPhase::new();
206    /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
207    /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
208    /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
209    /// // Raycast downward from (0, 10, 0)
210    /// let ray = Ray::new(Vector::new(0.0, 10.0, 0.0), Vector::new(0.0, -1.0, 0.0));
211    /// if let Some((handle, toi)) = query_pipeline.cast_ray(&ray, Real::MAX, true) {
212    ///     let hit_point = ray.origin + ray.dir * toi;
213    ///     println!("Hit at {:?}, distance = {}", hit_point, toi);
214    /// }
215    /// ```
216    #[profiling::function]
217    pub fn cast_ray(
218        &self,
219        ray: &Ray,
220        max_toi: Real,
221        solid: bool,
222    ) -> Option<(ColliderHandle, Real)> {
223        CompositeShapeRef(self)
224            .cast_local_ray(ray, max_toi, solid)
225            .and_then(|hit| self.id_to_handle(hit))
226    }
227
228    /// Casts a ray and returns detailed information about the hit (including surface normal).
229    ///
230    /// Like [`cast_ray()`](Self::cast_ray), but returns more information useful for things like:
231    /// - Decals (need surface normal to orient the texture)
232    /// - Bullet holes (need to know what part of the mesh was hit)
233    /// - Ricochets (need normal to calculate bounce direction)
234    ///
235    /// Returns `Some((handle, intersection))` where `intersection` contains:
236    /// - `toi`: Distance to impact
237    /// - `normal`: Surface normal at the hit point
238    /// - `feature`: Which geometric feature was hit (vertex, edge, face)
239    ///
240    /// # Example
241    /// ```
242    /// # use rapier3d::prelude::*;
243    /// # let mut bodies = RigidBodySet::new();
244    /// # let mut colliders = ColliderSet::new();
245    /// # let broad_phase = BroadPhaseBvh::new();
246    /// # let narrow_phase = NarrowPhase::new();
247    /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
248    /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
249    /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
250    /// # let ray = Ray::new(Vector::new(0.0, 10.0, 0.0), Vector::new(0.0, -1.0, 0.0));
251    /// if let Some((handle, hit)) = query_pipeline.cast_ray_and_get_normal(&ray, 100.0, true) {
252    ///     println!("Hit at distance {}, surface normal: {:?}", hit.time_of_impact, hit.normal);
253    /// }
254    /// ```
255    #[profiling::function]
256    pub fn cast_ray_and_get_normal(
257        &self,
258        ray: &Ray,
259        max_toi: Real,
260        solid: bool,
261    ) -> Option<(ColliderHandle, RayIntersection)> {
262        let (id, hit) =
263            CompositeShapeRef(self).cast_local_ray_and_get_normal(ray, max_toi, solid)?;
264        Some((self.handle_of(id)?, hit))
265    }
266
267    /// Returns ALL colliders that a ray passes through (not just the first).
268    ///
269    /// Unlike [`cast_ray()`](Self::cast_ray) which stops at the first hit, this returns
270    /// every collider along the ray's path. Useful for:
271    /// - Penetrating weapons that go through multiple objects
272    /// - Checking what's in a line (e.g., visibility through glass)
273    /// - Counting how many objects are between two points
274    ///
275    /// Returns an iterator of `(handle, collider, intersection)` tuples.
276    ///
277    /// # Example
278    /// ```
279    /// # use rapier3d::prelude::*;
280    /// # let mut bodies = RigidBodySet::new();
281    /// # let mut colliders = ColliderSet::new();
282    /// # let broad_phase = BroadPhaseBvh::new();
283    /// # let narrow_phase = NarrowPhase::new();
284    /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
285    /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
286    /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
287    /// # let ray = Ray::new(Vector::new(0.0, 10.0, 0.0), Vector::new(0.0, -1.0, 0.0));
288    /// for (handle, collider, hit) in query_pipeline.intersect_ray(ray, 100.0, true) {
289    ///     println!("Ray passed through {:?} at distance {}", handle, hit.time_of_impact);
290    /// }
291    /// ```
292    #[profiling::function]
293    pub fn intersect_ray(
294        &'a self,
295        ray: Ray,
296        max_toi: Real,
297        solid: bool,
298    ) -> impl Iterator<Item = (ColliderHandle, &'a Collider, RayIntersection)> + 'a {
299        // TODO: add this to CompositeShapeRef?
300        self.bvh
301            .leaves(move |node: &BvhNode| node.aabb().intersects_local_ray(&ray, max_toi))
302            .filter_map(move |leaf| {
303                let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
304                if self.filter.test(self.bodies, co_handle, co) {
305                    if let Some(intersection) =
306                        co.shape
307                            .cast_ray_and_get_normal(co.position(), &ray, max_toi, solid)
308                    {
309                        return Some((co_handle, co, intersection));
310                    }
311                }
312
313                None
314            })
315    }
316
317    /// Finds the closest point on any collider to the given point.
318    ///
319    /// Returns the collider and information about where on its surface the closest point is.
320    /// Useful for:
321    /// - Finding nearest cover/obstacle
322    /// - Snap-to-surface mechanics
323    /// - Distance queries
324    ///
325    /// # Parameters
326    /// * `solid` - If `true`, a point inside a shape projects to itself. If `false`, it projects
327    ///   to the nearest point on the shape's boundary
328    ///
329    /// # Example
330    /// ```
331    /// # use rapier3d::prelude::*;
332    /// # let params = IntegrationParameters::default();
333    /// # let mut bodies = RigidBodySet::new();
334    /// # let mut colliders = ColliderSet::new();
335    /// # let mut broad_phase = BroadPhaseBvh::new();
336    /// # let narrow_phase = NarrowPhase::new();
337    /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
338    /// # let ground_collider = ColliderBuilder::cuboid(10.0, 0.1, 10.0).build();
339    /// # let ground_aabb = ground_collider.compute_aabb();
340    /// # let collider_handle = colliders.insert_with_parent(ground_collider, ground, &mut bodies);
341    /// # broad_phase.set_aabb(&params, collider_handle, ground_aabb);
342    /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
343    /// let point = Vector::new(5.0, 0.0, 0.0);
344    /// if let Some((handle, projection)) = query_pipeline.project_point(point, std::f32::MAX, true) {
345    ///     println!("Closest collider: {:?}", handle);
346    ///     println!("Closest point: {:?}", projection.point);
347    ///     println!("Distance: {}", (point - projection.point).length());
348    /// }
349    /// ```
350    #[profiling::function]
351    pub fn project_point(
352        &self,
353        point: Vector,
354        max_dist: Real,
355        solid: bool,
356    ) -> Option<(ColliderHandle, PointProjection)> {
357        let (id, proj) = CompositeShapeRef(self).project_local_point(point, max_dist, solid)?;
358        Some((self.handle_of(id)?, proj))
359    }
360
361    /// Returns ALL colliders that contain the given point.
362    ///
363    /// A point is "inside" a collider if it's within its volume. Useful for:
364    /// - Detecting what area/trigger zones a point is in
365    /// - Checking if a position is inside geometry
366    /// - Finding all overlapping volumes at a location
367    ///
368    /// # Example
369    /// ```
370    /// # use rapier3d::prelude::*;
371    /// # let mut bodies = RigidBodySet::new();
372    /// # let mut colliders = ColliderSet::new();
373    /// # let broad_phase = BroadPhaseBvh::new();
374    /// # let narrow_phase = NarrowPhase::new();
375    /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
376    /// # colliders.insert_with_parent(ColliderBuilder::ball(5.0), ground, &mut bodies);
377    /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
378    /// let point = Vector::new(0.0, 0.0, 0.0);
379    /// for (handle, collider) in query_pipeline.intersect_point(point) {
380    ///     println!("Point is inside {:?}", handle);
381    /// }
382    /// ```
383    #[profiling::function]
384    pub fn intersect_point(
385        &'a self,
386        point: Vector,
387    ) -> impl Iterator<Item = (ColliderHandle, &'a Collider)> + 'a {
388        // TODO: add to CompositeShapeRef?
389        self.bvh
390            .leaves(move |node: &BvhNode| node.aabb().contains_local_point(point))
391            .filter_map(move |leaf| {
392                let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
393                if self.filter.test(self.bodies, co_handle, co)
394                    && co.shape.contains_point(co.position(), point)
395                {
396                    return Some((co_handle, co));
397                }
398
399                None
400            })
401    }
402
403    /// Find the projection of a point on the closest collider.
404    ///
405    /// The results include the ID of the feature hit by the point.
406    ///
407    /// # Parameters
408    /// * `point` - The point to project.
409    #[profiling::function]
410    pub fn project_point_and_get_feature(
411        &self,
412        point: Vector,
413        max_dist: Real,
414    ) -> Option<(ColliderHandle, PointProjection, FeatureId)> {
415        let (id, proj, feat) =
416            CompositeShapeRef(self).project_local_point_and_get_feature(point, max_dist)?;
417        Some((self.handle_of(id)?, proj, feat))
418    }
419
420    /// Finds all handles of all the colliders with an [`Aabb`] intersecting the given [`Aabb`].
421    ///
422    /// Note that the collider AABB taken into account is the one currently stored in the query
423    /// pipeline’s BVH. It doesn’t recompute the latest collider AABB.
424    #[profiling::function]
425    pub fn intersect_aabb_conservative(
426        &'a self,
427        aabb: Aabb,
428    ) -> impl Iterator<Item = (ColliderHandle, &'a Collider)> + 'a {
429        // TODO: add to ColliderRef?
430        self.bvh
431            .leaves(move |node: &BvhNode| node.aabb().intersects(&aabb))
432            .filter_map(move |leaf| {
433                let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
434                // NOTE: do **not** recompute and check the latest collider AABB.
435                //       Checking only against the one in the BVH is useful, e.g., for conservative
436                //       scene queries for CCD.
437                if self.filter.test(self.bodies, co_handle, co) {
438                    return Some((co_handle, co));
439                }
440
441                None
442            })
443    }
444
445    /// Sweeps a shape through the world to find what it would collide with.
446    ///
447    /// Like raycasting, but instead of a thin ray, you're moving an entire shape (sphere, box, etc.)
448    /// through space. This is also called "shape casting" or "sweep testing". Useful for:
449    /// - Predicting where a moving object will hit something
450    /// - Checking if a movement is valid before executing it
451    /// - Thick raycasts (e.g., character controller collision prediction)
452    /// - Area-of-effect scanning along a path
453    ///
454    /// Returns the first collision: `(collider_handle, hit_details)` where hit contains
455    /// time-of-impact, witness points, and surface normal.
456    ///
457    /// In the returned [`ShapeCastHit`], `witness1` and `normal1` refer to the hit collider
458    /// and are expressed in **world space**. `witness2` and `normal2` refer to the cast shape
459    /// and are expressed in its **local space** (relative to `shape_pos`).
460    ///
461    /// # Parameters
462    /// * `shape_pos` - Starting position/orientation of the shape
463    /// * `shape_vel` - Direction and speed to move the shape (velocity vector)
464    /// * `shape` - The shape to sweep (ball, cuboid, capsule, etc.)
465    /// * `options` - Maximum distance, collision filtering, etc.
466    ///
467    /// # Example
468    /// ```
469    /// # use rapier3d::prelude::*;
470    /// # use rapier3d::parry::{query::ShapeCastOptions, shape::Ball};
471    /// # let mut bodies = RigidBodySet::new();
472    /// # let mut colliders = ColliderSet::new();
473    /// # let narrow_phase = NarrowPhase::new();
474    /// # let broad_phase = BroadPhaseBvh::new();
475    /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
476    /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
477    /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
478    /// // Sweep a sphere downward
479    /// let shape = Ball::new(0.5);
480    /// let start_pos = Pose::translation(0.0, 10.0, 0.0);
481    /// let velocity = Vector::new(0.0, -1.0, 0.0);
482    /// let options = ShapeCastOptions::default();
483    ///
484    /// if let Some((handle, hit)) = query_pipeline.cast_shape(&start_pos, velocity, &shape, options) {
485    ///     println!("Shape would hit {:?} at time {}", handle, hit.time_of_impact);
486    /// }
487    /// ```
488    #[profiling::function]
489    pub fn cast_shape(
490        &self,
491        shape_pos: &Pose,
492        shape_vel: Vector,
493        shape: &dyn Shape,
494        options: ShapeCastOptions,
495    ) -> Option<(ColliderHandle, ShapeCastHit)> {
496        let (id, hit) = CompositeShapeRef(self).cast_shape(
497            self.dispatcher,
498            shape_pos,
499            shape_vel,
500            shape,
501            options,
502        )?;
503        Some((self.handle_of(id)?, hit))
504    }
505
506    /// Casts a shape with an arbitrary continuous motion and retrieve the first collider it hits.
507    ///
508    /// In the returned [`ShapeCastHit`], `witness1` and `normal1` refer to the hit collider
509    /// and are expressed in **world space**. `witness2` and `normal2` refer to the cast shape
510    /// and are expressed in its **local space** (they follow the shape along `shape_motion`).
511    ///
512    /// # Parameters
513    /// * `shape_motion` - The motion of the shape.
514    /// * `shape` - The shape to cast.
515    /// * `start_time` - The starting time of the interval where the motion takes place.
516    /// * `end_time` - The end time of the interval where the motion takes place.
517    /// * `stop_at_penetration` - If the casted shape starts in a penetration state with any
518    ///    collider, two results are possible. If `stop_at_penetration` is `true` then, the
519    ///    result will have a `toi` equal to `start_time`. If `stop_at_penetration` is `false`
520    ///    then the nonlinear shape-casting will see if further motion with respect to the penetration normal
521    ///    would result in tunnelling. If it does not (i.e. we have a separating velocity along
522    ///    that normal) then the nonlinear shape-casting will attempt to find another impact,
523    ///    at a time `> start_time` that could result in tunnelling.
524    #[profiling::function]
525    pub fn cast_shape_nonlinear(
526        &self,
527        shape_motion: &NonlinearRigidMotion,
528        shape: &dyn Shape,
529        start_time: Real,
530        end_time: Real,
531        stop_at_penetration: bool,
532    ) -> Option<(ColliderHandle, ShapeCastHit)> {
533        let (id, hit) = CompositeShapeRef(self).cast_shape_nonlinear(
534            self.dispatcher,
535            &NonlinearRigidMotion::identity(),
536            shape_motion,
537            shape,
538            start_time,
539            end_time,
540            stop_at_penetration,
541        )?;
542        Some((self.handle_of(id)?, hit))
543    }
544
545    /// Retrieve all the colliders intersecting the given shape.
546    ///
547    /// # Parameters
548    /// * `shapePos` - The pose of the shape to test.
549    /// * `shape` - The shape to test.
550    #[profiling::function]
551    pub fn intersect_shape(
552        &'a self,
553        shape_pos: Pose,
554        shape: &'a dyn Shape,
555    ) -> impl Iterator<Item = (ColliderHandle, &'a Collider)> + 'a {
556        // TODO: add this to CompositeShapeRef?
557        let shape_aabb = shape.compute_aabb(&shape_pos);
558        self.bvh
559            .leaves(move |node: &BvhNode| node.aabb().intersects(&shape_aabb))
560            .filter_map(move |leaf| {
561                let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
562                if self.filter.test(self.bodies, co_handle, co) {
563                    let pos12 = shape_pos.inv_mul(co.position());
564                    if self
565                        .dispatcher
566                        .intersection_test(&pos12, shape, co.shape())
567                        .is_ok_and(|hit| hit.intersecting)
568                    {
569                        return Some((co_handle, co));
570                    }
571                }
572
573                None
574            })
575    }
576}
577
578bitflags::bitflags! {
579    #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
580    /// Flags for filtering spatial queries by body type or sensor status.
581    ///
582    /// Use these to quickly exclude categories of colliders from raycasts and other queries.
583    ///
584    /// # Example
585    /// ```
586    /// # use rapier3d::prelude::*;
587    /// // Raycast that only hits dynamic objects (ignore walls/floors)
588    /// let filter = QueryFilter::from(QueryFilterFlags::ONLY_DYNAMIC);
589    ///
590    /// // Find only trigger zones, not solid geometry
591    /// let filter = QueryFilter::from(QueryFilterFlags::EXCLUDE_SOLIDS);
592    /// ```
593    pub struct QueryFilterFlags: u32 {
594        /// Excludes fixed bodies and standalone colliders.
595        const EXCLUDE_FIXED = 1 << 0;
596        /// Excludes kinematic bodies.
597        const EXCLUDE_KINEMATIC = 1 << 1;
598        /// Excludes dynamic bodies.
599        const EXCLUDE_DYNAMIC = 1 << 2;
600        /// Excludes sensors (trigger zones).
601        const EXCLUDE_SENSORS = 1 << 3;
602        /// Excludes solid colliders (only hit sensors).
603        const EXCLUDE_SOLIDS = 1 << 4;
604        /// Only includes dynamic bodies.
605        const ONLY_DYNAMIC = Self::EXCLUDE_FIXED.bits() | Self::EXCLUDE_KINEMATIC.bits();
606        /// Only includes kinematic bodies.
607        const ONLY_KINEMATIC = Self::EXCLUDE_DYNAMIC.bits() | Self::EXCLUDE_FIXED.bits();
608        /// Only includes fixed bodies (excluding standalone colliders).
609        const ONLY_FIXED = Self::EXCLUDE_DYNAMIC.bits() | Self::EXCLUDE_KINEMATIC.bits();
610    }
611}
612
613impl QueryFilterFlags {
614    /// Tests if the given collider should be taken into account by a scene query, based
615    /// on the flags on `self`.
616    #[inline]
617    pub fn test(&self, bodies: &RigidBodySet, collider: &Collider) -> bool {
618        if self.is_empty() {
619            // No filter.
620            return true;
621        }
622
623        if (self.contains(QueryFilterFlags::EXCLUDE_SENSORS) && collider.is_sensor())
624            || (self.contains(QueryFilterFlags::EXCLUDE_SOLIDS) && !collider.is_sensor())
625        {
626            return false;
627        }
628
629        if self.contains(QueryFilterFlags::EXCLUDE_FIXED) && collider.parent.is_none() {
630            return false;
631        }
632
633        if let Some(parent) = collider.parent.and_then(|p| bodies.get(p.handle)) {
634            let parent_type = parent.body_type();
635
636            if (self.contains(QueryFilterFlags::EXCLUDE_FIXED) && parent_type.is_fixed())
637                || (self.contains(QueryFilterFlags::EXCLUDE_KINEMATIC)
638                    && parent_type.is_kinematic())
639                || (self.contains(QueryFilterFlags::EXCLUDE_DYNAMIC) && parent_type.is_dynamic())
640            {
641                return false;
642            }
643        }
644
645        true
646    }
647}
648
649/// Filtering rules for spatial queries (raycasts, shape casts, etc.).
650///
651/// Controls which colliders should be included/excluded from query results.
652/// By default, all colliders are included.
653///
654/// # Common filters
655///
656/// ```
657/// # use rapier3d::prelude::*;
658/// # let player_collider = ColliderHandle::from_raw_parts(0, 0);
659/// # let enemy_groups = InteractionGroups::all();
660/// // Only hit dynamic objects (ignore static walls)
661/// let filter = QueryFilter::only_dynamic();
662///
663/// // Hit everything except the player's own collider
664/// let filter = QueryFilter::default()
665///     .exclude_collider(player_collider);
666///
667/// // Raycast that only hits enemies (using collision groups)
668/// let filter = QueryFilter::default()
669///     .groups(enemy_groups);
670///
671/// // Custom filtering with a closure
672/// let filter = QueryFilter::default()
673///     .predicate(&|handle, collider| {
674///         // Only hit colliders with user_data > 100
675///         collider.user_data > 100
676///     });
677/// ```
678#[derive(Copy, Clone, Default)]
679pub struct QueryFilter<'a> {
680    /// Flags for excluding fixed/kinematic/dynamic bodies or sensors/solids.
681    pub flags: QueryFilterFlags,
682    /// If set, only colliders with compatible collision groups are included.
683    pub groups: Option<InteractionGroups>,
684    /// If set, this specific collider is excluded.
685    pub exclude_collider: Option<ColliderHandle>,
686    /// If set, all colliders attached to this body are excluded.
687    pub exclude_rigid_body: Option<RigidBodyHandle>,
688    /// Custom filtering function - collider included only if this returns `true`.
689    #[allow(clippy::type_complexity)]
690    pub predicate: Option<&'a dyn Fn(ColliderHandle, &Collider) -> bool>,
691}
692
693impl QueryFilter<'_> {
694    /// Applies the filters described by `self` to a collider to determine if it has to be
695    /// included in a scene query (`true`) or not (`false`).
696    #[inline]
697    pub fn test(&self, bodies: &RigidBodySet, handle: ColliderHandle, collider: &Collider) -> bool {
698        self.exclude_collider != Some(handle)
699            && (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.
700            || self.exclude_rigid_body != collider.parent.map(|p| p.handle))
701            && self
702                .groups
703                .map(|grps| collider.flags.collision_groups.test(grps))
704                .unwrap_or(true)
705            && self.flags.test(bodies, collider)
706            && self.predicate.map(|f| f(handle, collider)).unwrap_or(true)
707    }
708}
709
710impl From<QueryFilterFlags> for QueryFilter<'_> {
711    fn from(flags: QueryFilterFlags) -> Self {
712        Self {
713            flags,
714            ..QueryFilter::default()
715        }
716    }
717}
718
719impl From<InteractionGroups> for QueryFilter<'_> {
720    fn from(groups: InteractionGroups) -> Self {
721        Self {
722            groups: Some(groups),
723            ..QueryFilter::default()
724        }
725    }
726}
727
728impl<'a> QueryFilter<'a> {
729    /// A query filter that doesn’t exclude any collider.
730    pub fn new() -> Self {
731        Self::default()
732    }
733
734    /// Exclude from the query any collider attached to a fixed rigid-body and colliders with no rigid-body attached.
735    pub fn exclude_fixed() -> Self {
736        QueryFilterFlags::EXCLUDE_FIXED.into()
737    }
738
739    /// Exclude from the query any collider attached to a kinematic rigid-body.
740    pub fn exclude_kinematic() -> Self {
741        QueryFilterFlags::EXCLUDE_KINEMATIC.into()
742    }
743
744    /// Exclude from the query any collider attached to a dynamic rigid-body.
745    pub fn exclude_dynamic() -> Self {
746        QueryFilterFlags::EXCLUDE_DYNAMIC.into()
747    }
748
749    /// Excludes all colliders not attached to a dynamic rigid-body.
750    pub fn only_dynamic() -> Self {
751        QueryFilterFlags::ONLY_DYNAMIC.into()
752    }
753
754    /// Excludes all colliders not attached to a kinematic rigid-body.
755    pub fn only_kinematic() -> Self {
756        QueryFilterFlags::ONLY_KINEMATIC.into()
757    }
758
759    /// Exclude all colliders attached to a non-fixed rigid-body
760    /// (this will not exclude colliders not attached to any rigid-body).
761    pub fn only_fixed() -> Self {
762        QueryFilterFlags::ONLY_FIXED.into()
763    }
764
765    /// Exclude from the query any collider that is a sensor.
766    pub fn exclude_sensors(mut self) -> Self {
767        self.flags |= QueryFilterFlags::EXCLUDE_SENSORS;
768        self
769    }
770
771    /// Exclude from the query any collider that is not a sensor.
772    pub fn exclude_solids(mut self) -> Self {
773        self.flags |= QueryFilterFlags::EXCLUDE_SOLIDS;
774        self
775    }
776
777    /// Only colliders with collision groups compatible with this one will
778    /// be included in the scene query.
779    pub fn groups(mut self, groups: InteractionGroups) -> Self {
780        self.groups = Some(groups);
781        self
782    }
783
784    /// Set the collider that will be excluded from the scene query.
785    pub fn exclude_collider(mut self, collider: ColliderHandle) -> Self {
786        self.exclude_collider = Some(collider);
787        self
788    }
789
790    /// Set the rigid-body that will be excluded from the scene query.
791    pub fn exclude_rigid_body(mut self, rigid_body: RigidBodyHandle) -> Self {
792        self.exclude_rigid_body = Some(rigid_body);
793        self
794    }
795
796    /// Set the predicate to apply a custom collider filtering during the scene query.
797    pub fn predicate(mut self, predicate: &'a impl Fn(ColliderHandle, &Collider) -> bool) -> Self {
798        self.predicate = Some(predicate);
799        self
800    }
801}
802
803#[cfg(test)]
804mod test {
805    use super::QueryFilter;
806    use crate::dynamics::{
807        CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet,
808        RigidBodyBuilder, RigidBodySet, SoftBodySet,
809    };
810    use crate::geometry::{BroadPhaseBvh, ColliderBuilder, ColliderSet, NarrowPhase};
811    use crate::math::{Real, Vector};
812    use crate::pipeline::PhysicsPipeline;
813
814    /// Steps the pipeline once so that `broad_phase` ends up with an up-to-date BVH.
815    fn step(
816        bodies: &mut RigidBodySet,
817        colliders: &mut ColliderSet,
818        broad_phase: &mut BroadPhaseBvh,
819        narrow_phase: &mut NarrowPhase,
820    ) {
821        PhysicsPipeline::new().step(
822            Vector::ZERO,
823            &IntegrationParameters::default(),
824            &mut IslandManager::new(),
825            broad_phase,
826            narrow_phase,
827            bodies,
828            colliders,
829            &mut ImpulseJointSet::new(),
830            &mut MultibodyJointSet::new(),
831            &mut SoftBodySet::new(),
832            &mut CCDSolver::new(),
833            &(),
834            &(),
835        );
836    }
837
838    /// Regression test for issues #923 and #926: projecting a point when there is no
839    /// collider to project onto must return `None` instead of panicking.
840    #[test]
841    fn project_point_on_empty_pipeline_returns_none() {
842        let mut bodies = RigidBodySet::new();
843        let mut colliders = ColliderSet::new();
844        let mut broad_phase = BroadPhaseBvh::new();
845        let mut narrow_phase = NarrowPhase::new();
846        step(
847            &mut bodies,
848            &mut colliders,
849            &mut broad_phase,
850            &mut narrow_phase,
851        );
852
853        let qp = broad_phase.as_query_pipeline(
854            narrow_phase.query_dispatcher(),
855            &bodies,
856            &colliders,
857            QueryFilter::default(),
858        );
859        assert!(qp.project_point(Vector::ZERO, Real::MAX, true).is_none());
860        assert!(
861            qp.project_point_and_get_feature(Vector::ZERO, Real::MAX)
862                .is_none()
863        );
864    }
865
866    /// Regression test for issue #923: when every collider is removed by the query
867    /// filter, `project_point` must return `None` instead of panicking.
868    #[test]
869    fn project_point_with_all_colliders_filtered_returns_none() {
870        let mut bodies = RigidBodySet::new();
871        let mut colliders = ColliderSet::new();
872        let mut broad_phase = BroadPhaseBvh::new();
873        let mut narrow_phase = NarrowPhase::new();
874
875        // A standalone (non-dynamic) collider.
876        colliders.insert(ColliderBuilder::ball(1.0));
877        step(
878            &mut bodies,
879            &mut colliders,
880            &mut broad_phase,
881            &mut narrow_phase,
882        );
883
884        // `only_dynamic` excludes the standalone collider, leaving nothing to project onto.
885        let qp = broad_phase.as_query_pipeline(
886            narrow_phase.query_dispatcher(),
887            &bodies,
888            &colliders,
889            QueryFilter::only_dynamic(),
890        );
891        assert!(qp.project_point(Vector::ZERO, Real::MAX, true).is_none());
892        assert!(
893            qp.project_point_and_get_feature(Vector::ZERO, Real::MAX)
894                .is_none()
895        );
896    }
897
898    /// Regression test for issue #921: `project_point` must honor its `max_dist` parameter.
899    #[test]
900    fn project_point_respects_max_dist() {
901        let mut bodies = RigidBodySet::new();
902        let mut colliders = ColliderSet::new();
903        let mut broad_phase = BroadPhaseBvh::new();
904        let mut narrow_phase = NarrowPhase::new();
905
906        // A unit ball at the origin.
907        let body = bodies.insert(RigidBodyBuilder::fixed());
908        let handle = colliders.insert_with_parent(ColliderBuilder::ball(1.0), body, &mut bodies);
909        step(
910            &mut bodies,
911            &mut colliders,
912            &mut broad_phase,
913            &mut narrow_phase,
914        );
915
916        let qp = broad_phase.as_query_pipeline(
917            narrow_phase.query_dispatcher(),
918            &bodies,
919            &colliders,
920            QueryFilter::default(),
921        );
922
923        // Point 10 units away along X: its closest point on the unit ball is 9 units away.
924        let point = Vector::X * 10.0;
925
926        // `max_dist` smaller than the actual distance => no result.
927        assert!(qp.project_point(point, 1.0, false).is_none());
928
929        // `max_dist` larger than the actual distance => the ball is found...
930        let (found, proj) = qp.project_point(point, 100.0, false).unwrap();
931        assert_eq!(found, handle);
932        // ...and the projection lies within `max_dist` of the queried point.
933        assert!((proj.point - point).length() <= 100.0);
934    }
935}