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(¶ms, 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}