rapier2d/pipeline/query_pipeline.rs
1use crate::dynamics::RigidBodyHandle;
2use crate::geometry::{Aabb, Collider, ColliderHandle, PointProjection, Ray, RayIntersection};
3use crate::geometry::{BroadPhaseBvh, InteractionGroups};
4use crate::math::{Pose, Real, Vector};
5use crate::{dynamics::RigidBodySet, geometry::ColliderSet};
6use parry::bounding_volume::BoundingVolume;
7use parry::partitioning::{Bvh, BvhNode};
8use parry::query::details::{NormalConstraints, ShapeCastOptions};
9use parry::query::{NonlinearRigidMotion, QueryDispatcher, RayCast, ShapeCastHit};
10use parry::shape::{CompositeShape, CompositeShapeRef, FeatureId, Shape, TypedCompositeShape};
11
12/// A query system for performing spatial queries on your physics world (raycasts, shape casts, intersections).
13///
14/// Think of this as a "search engine" for your physics world. Use it to answer questions like:
15/// - "What does this ray hit?"
16/// - "What colliders are near this point?"
17/// - "If I move this shape, what will it collide with?"
18///
19/// Get a QueryPipeline from your [`BroadPhaseBvh`] using [`as_query_pipeline()`](BroadPhaseBvh::as_query_pipeline).
20///
21/// # Example
22/// ```
23/// # use rapier3d::prelude::*;
24/// # let mut bodies = RigidBodySet::new();
25/// # let mut colliders = ColliderSet::new();
26/// # let broad_phase = BroadPhaseBvh::new();
27/// # let narrow_phase = NarrowPhase::new();
28/// # let ground = bodies.insert(RigidBodyBuilder::fixed());
29/// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
30/// let query_pipeline = broad_phase.as_query_pipeline(
31/// narrow_phase.query_dispatcher(),
32/// &bodies,
33/// &colliders,
34/// QueryFilter::default()
35/// );
36///
37/// // Cast a ray downward
38/// let ray = Ray::new(Vector::new(0.0, 10.0, 0.0), Vector::new(0.0, -1.0, 0.0));
39/// if let Some((handle, toi)) = query_pipeline.cast_ray(&ray, Real::MAX, false) {
40/// println!("Hit collider {:?} at distance {}", handle, toi);
41/// }
42/// ```
43#[derive(Copy, Clone)]
44pub struct QueryPipeline<'a> {
45 /// The query dispatcher for running geometric queries on leaf geometries.
46 pub dispatcher: &'a dyn QueryDispatcher,
47 /// A bvh containing collider indices at its leaves.
48 pub bvh: &'a Bvh,
49 /// Rigid-bodies potentially involved in the scene queries.
50 pub bodies: &'a RigidBodySet,
51 /// Colliders potentially involved in the scene queries.
52 pub colliders: &'a ColliderSet,
53 /// The query filters for controlling what colliders should be ignored by the queries.
54 pub filter: QueryFilter<'a>,
55}
56
57/// Same as [`QueryPipeline`] but holds mutable references to the body and collider sets.
58///
59/// This structure is generally obtained by calling [`BroadPhaseBvh::as_query_pipeline_mut`].
60/// This is useful for argument passing. Call `.as_ref()` for obtaining a `QueryPipeline`
61/// to run the scene queries.
62pub struct QueryPipelineMut<'a> {
63 /// The query dispatcher for running geometric queries on leaf geometries.
64 pub dispatcher: &'a dyn QueryDispatcher,
65 /// A bvh containing collider indices at its leaves.
66 pub bvh: &'a Bvh,
67 /// Rigid-bodies potentially involved in the scene queries.
68 pub bodies: &'a mut RigidBodySet,
69 /// Colliders potentially involved in the scene queries.
70 pub colliders: &'a mut ColliderSet,
71 /// The query filters for controlling what colliders should be ignored by the queries.
72 pub filter: QueryFilter<'a>,
73}
74
75impl QueryPipelineMut<'_> {
76 /// Downgrades the mutable reference to an immutable reference.
77 pub fn as_ref(&self) -> QueryPipeline<'_> {
78 QueryPipeline {
79 dispatcher: self.dispatcher,
80 bvh: self.bvh,
81 bodies: &*self.bodies,
82 colliders: &*self.colliders,
83 filter: self.filter,
84 }
85 }
86}
87
88impl CompositeShape for QueryPipeline<'_> {
89 fn map_part_at(
90 &self,
91 shape_id: u32,
92 f: &mut dyn FnMut(Option<&Pose>, &dyn Shape, Option<&dyn NormalConstraints>),
93 ) {
94 self.map_untyped_part_at(shape_id, f);
95 }
96 fn bvh(&self) -> &Bvh {
97 self.bvh
98 }
99}
100
101impl TypedCompositeShape for QueryPipeline<'_> {
102 type PartNormalConstraints = ();
103 type PartShape = dyn Shape;
104 fn map_typed_part_at<T>(
105 &self,
106 shape_id: u32,
107 mut f: impl FnMut(Option<&Pose>, &Self::PartShape, Option<&Self::PartNormalConstraints>) -> T,
108 ) -> Option<T> {
109 let (co, co_handle) = self.colliders.get_unknown_gen(shape_id)?;
110
111 if self.filter.test(self.bodies, co_handle, co) {
112 Some(f(Some(co.position()), co.shape(), None))
113 } else {
114 None
115 }
116 }
117
118 fn map_untyped_part_at<T>(
119 &self,
120 shape_id: u32,
121 mut f: impl FnMut(Option<&Pose>, &dyn Shape, Option<&dyn NormalConstraints>) -> T,
122 ) -> Option<T> {
123 let (co, co_handle) = self.colliders.get_unknown_gen(shape_id)?;
124
125 if self.filter.test(self.bodies, co_handle, co) {
126 Some(f(Some(co.position()), co.shape(), None))
127 } else {
128 None
129 }
130 }
131}
132
133impl BroadPhaseBvh {
134 /// Initialize a [`QueryPipeline`] for scene queries from this broad-phase.
135 pub fn as_query_pipeline<'a>(
136 &'a self,
137 dispatcher: &'a dyn QueryDispatcher,
138 bodies: &'a RigidBodySet,
139 colliders: &'a ColliderSet,
140 filter: QueryFilter<'a>,
141 ) -> QueryPipeline<'a> {
142 QueryPipeline {
143 dispatcher,
144 bvh: &self.tree,
145 bodies,
146 colliders,
147 filter,
148 }
149 }
150
151 /// Initialize a [`QueryPipelineMut`] for scene queries from this broad-phase.
152 pub fn as_query_pipeline_mut<'a>(
153 &'a self,
154 dispatcher: &'a dyn QueryDispatcher,
155 bodies: &'a mut RigidBodySet,
156 colliders: &'a mut ColliderSet,
157 filter: QueryFilter<'a>,
158 ) -> QueryPipelineMut<'a> {
159 QueryPipelineMut {
160 dispatcher,
161 bvh: &self.tree,
162 bodies,
163 colliders,
164 filter,
165 }
166 }
167}
168
169impl<'a> QueryPipeline<'a> {
170 fn id_to_handle<T>(&self, (id, data): (u32, T)) -> Option<(ColliderHandle, T)> {
171 self.colliders.get_unknown_gen(id).map(|(_, h)| (h, data))
172 }
173
174 /// Replaces [`Self::filter`] with different filtering rules.
175 pub fn with_filter(self, filter: QueryFilter<'a>) -> Self {
176 Self { filter, ..self }
177 }
178
179 /// Casts a ray through the world and returns the first collider it hits.
180 ///
181 /// This is one of the most common operations - use it for line-of-sight checks,
182 /// projectile trajectories, mouse picking, laser beams, etc.
183 ///
184 /// Returns `Some((handle, distance))` if the ray hits something, where:
185 /// - `handle` is which collider was hit
186 /// - `distance` is how far along the ray the hit occurred (time-of-impact)
187 ///
188 /// # Parameters
189 /// * `ray` - The ray to cast (origin + direction). Create with `Ray::new(origin, direction)`
190 /// * `max_toi` - Maximum distance to check. Use `Real::MAX` for unlimited range
191 /// * `solid` - If `true`, detects hits even if the ray starts inside a shape. If `false`,
192 /// the ray "passes through" from the inside until it exits
193 ///
194 /// # Example
195 /// ```
196 /// # use rapier3d::prelude::*;
197 /// # let mut bodies = RigidBodySet::new();
198 /// # let mut colliders = ColliderSet::new();
199 /// # let broad_phase = BroadPhaseBvh::new();
200 /// # let narrow_phase = NarrowPhase::new();
201 /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
202 /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
203 /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
204 /// // Raycast downward from (0, 10, 0)
205 /// let ray = Ray::new(Vector::new(0.0, 10.0, 0.0), Vector::new(0.0, -1.0, 0.0));
206 /// if let Some((handle, toi)) = query_pipeline.cast_ray(&ray, Real::MAX, true) {
207 /// let hit_point = ray.origin + ray.dir * toi;
208 /// println!("Hit at {:?}, distance = {}", hit_point, toi);
209 /// }
210 /// ```
211 #[profiling::function]
212 pub fn cast_ray(
213 &self,
214 ray: &Ray,
215 max_toi: Real,
216 solid: bool,
217 ) -> Option<(ColliderHandle, Real)> {
218 CompositeShapeRef(self)
219 .cast_local_ray(ray, max_toi, solid)
220 .and_then(|hit| self.id_to_handle(hit))
221 }
222
223 /// Casts a ray and returns detailed information about the hit (including surface normal).
224 ///
225 /// Like [`cast_ray()`](Self::cast_ray), but returns more information useful for things like:
226 /// - Decals (need surface normal to orient the texture)
227 /// - Bullet holes (need to know what part of the mesh was hit)
228 /// - Ricochets (need normal to calculate bounce direction)
229 ///
230 /// Returns `Some((handle, intersection))` where `intersection` contains:
231 /// - `toi`: Distance to impact
232 /// - `normal`: Surface normal at the hit point
233 /// - `feature`: Which geometric feature was hit (vertex, edge, face)
234 ///
235 /// # Example
236 /// ```
237 /// # use rapier3d::prelude::*;
238 /// # let mut bodies = RigidBodySet::new();
239 /// # let mut colliders = ColliderSet::new();
240 /// # let broad_phase = BroadPhaseBvh::new();
241 /// # let narrow_phase = NarrowPhase::new();
242 /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
243 /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
244 /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
245 /// # let ray = Ray::new(Vector::new(0.0, 10.0, 0.0), Vector::new(0.0, -1.0, 0.0));
246 /// if let Some((handle, hit)) = query_pipeline.cast_ray_and_get_normal(&ray, 100.0, true) {
247 /// println!("Hit at distance {}, surface normal: {:?}", hit.time_of_impact, hit.normal);
248 /// }
249 /// ```
250 #[profiling::function]
251 pub fn cast_ray_and_get_normal(
252 &self,
253 ray: &Ray,
254 max_toi: Real,
255 solid: bool,
256 ) -> Option<(ColliderHandle, RayIntersection)> {
257 CompositeShapeRef(self)
258 .cast_local_ray_and_get_normal(ray, max_toi, solid)
259 .and_then(|hit| self.id_to_handle(hit))
260 }
261
262 /// Returns ALL colliders that a ray passes through (not just the first).
263 ///
264 /// Unlike [`cast_ray()`](Self::cast_ray) which stops at the first hit, this returns
265 /// every collider along the ray's path. Useful for:
266 /// - Penetrating weapons that go through multiple objects
267 /// - Checking what's in a line (e.g., visibility through glass)
268 /// - Counting how many objects are between two points
269 ///
270 /// Returns an iterator of `(handle, collider, intersection)` tuples.
271 ///
272 /// # Example
273 /// ```
274 /// # use rapier3d::prelude::*;
275 /// # let mut bodies = RigidBodySet::new();
276 /// # let mut colliders = ColliderSet::new();
277 /// # let broad_phase = BroadPhaseBvh::new();
278 /// # let narrow_phase = NarrowPhase::new();
279 /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
280 /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies);
281 /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default());
282 /// # let ray = Ray::new(Vector::new(0.0, 10.0, 0.0), Vector::new(0.0, -1.0, 0.0));
283 /// for (handle, collider, hit) in query_pipeline.intersect_ray(ray, 100.0, true) {
284 /// println!("Ray passed through {:?} at distance {}", handle, hit.time_of_impact);
285 /// }
286 /// ```
287 #[profiling::function]
288 pub fn intersect_ray(
289 &'a self,
290 ray: Ray,
291 max_toi: Real,
292 solid: bool,
293 ) -> impl Iterator<Item = (ColliderHandle, &'a Collider, RayIntersection)> + 'a {
294 // TODO: add this to CompositeShapeRef?
295 self.bvh
296 .leaves(move |node: &BvhNode| node.aabb().intersects_local_ray(&ray, max_toi))
297 .filter_map(move |leaf| {
298 let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
299 if self.filter.test(self.bodies, co_handle, co) {
300 if let Some(intersection) =
301 co.shape
302 .cast_ray_and_get_normal(co.position(), &ray, max_toi, solid)
303 {
304 return Some((co_handle, co, intersection));
305 }
306 }
307
308 None
309 })
310 }
311
312 /// Finds the closest point on any collider to the given point.
313 ///
314 /// Returns the collider and information about where on its surface the closest point is.
315 /// Useful for:
316 /// - Finding nearest cover/obstacle
317 /// - Snap-to-surface mechanics
318 /// - Distance queries
319 ///
320 /// # Parameters
321 /// * `solid` - If `true`, a point inside a shape projects to itself. If `false`, it projects
322 /// to the nearest point on the shape's boundary
323 ///
324 /// # Example
325 /// ```
326 /// # use rapier3d::prelude::*;
327 /// # let params = IntegrationParameters::default();
328 /// # let mut bodies = RigidBodySet::new();
329 /// # let mut colliders = ColliderSet::new();
330 /// # let mut broad_phase = BroadPhaseBvh::new();
331 /// # let narrow_phase = NarrowPhase::new();
332 /// # let ground = bodies.insert(RigidBodyBuilder::fixed());
333 /// # let ground_collider = ColliderBuilder::cuboid(10.0, 0.1, 10.0).build();
334 /// # let ground_aabb = ground_collider.compute_aabb();
335 /// # let collider_handle = colliders.insert_with_parent(ground_collider, ground, &mut bodies);
336 /// # broad_phase.set_aabb(¶ms, 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}