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