Skip to main content

avian3d/spatial_query/
system_param.rs

1use crate::{collider_tree::ColliderTrees, collision::collider::contact_query, prelude::*};
2use bevy::{ecs::system::SystemParam, prelude::*};
3use parry::query::ShapeCastOptions;
4
5/// A system parameter for performing [spatial queries](spatial_query).
6///
7/// # Methods
8///
9/// - [Raycasting](spatial_query#raycasting): [`cast_ray`](SpatialQuery::cast_ray), [`cast_ray_predicate`](SpatialQuery::cast_ray_predicate),
10///   [`ray_hits`](SpatialQuery::ray_hits), [`ray_hits_callback`](SpatialQuery::ray_hits_callback)
11/// - [Shapecasting](spatial_query#shapecasting): [`cast_shape`](SpatialQuery::cast_shape), [`cast_shape_predicate`](SpatialQuery::cast_shape_predicate),
12///   [`shape_hits`](SpatialQuery::shape_hits), [`shape_hits_callback`](SpatialQuery::shape_hits_callback)
13/// - [Point projection](spatial_query#point-projection): [`project_point`](SpatialQuery::project_point) and [`project_point_predicate`](SpatialQuery::project_point_predicate)
14/// - [Intersection tests](spatial_query#intersection-tests)
15///     - Point intersections: [`point_intersections`](SpatialQuery::point_intersections),
16///       [`point_intersections_callback`](SpatialQuery::point_intersections_callback)
17///     - AABB intersections: [`aabb_intersections_with_aabb`](SpatialQuery::aabb_intersections_with_aabb),
18///       [`aabb_intersections_with_aabb_callback`](SpatialQuery::aabb_intersections_with_aabb_callback)
19///     - Shape intersections: [`shape_intersections`](SpatialQuery::shape_intersections)
20///       [`shape_intersections_callback`](SpatialQuery::shape_intersections_callback)
21///
22/// For simple raycasts and shapecasts, consider using the [`RayCaster`] and [`ShapeCaster`] components that
23/// provide a more ECS-based approach and perform casts on every frame.
24///
25/// # Example
26///
27/// ```
28/// # #[cfg(feature = "2d")]
29/// # use avian2d::prelude::*;
30/// # #[cfg(feature = "3d")]
31/// use avian3d::{math::RVec3, prelude::*};
32/// use bevy::prelude::*;
33///
34/// # #[cfg(feature = "3d")]
35/// fn print_hits(spatial_query: SpatialQuery) {
36///     // Ray origin and direction
37///     let origin = RVec3::ZERO;
38///     let direction = Dir3::X;
39///
40///     // Configuration for the ray cast
41///     let max_distance = 100.0;
42///     let solid = true;
43///     let filter = SpatialQueryFilter::default();
44///
45///     // Cast ray and print first hit
46///     if let Some(first_hit) = spatial_query.cast_ray(origin, direction, max_distance, solid, &filter) {
47///         println!("First hit: {:?}", first_hit);
48///     }
49///
50///     // Cast ray and get up to 20 hits
51///     let hits = spatial_query.ray_hits(origin, direction, max_distance, 20, solid, &filter);
52///
53///     // Print hits
54///     for hit in hits.iter() {
55///         println!("Hit: {:?}", hit);
56///     }
57/// }
58/// ```
59#[derive(SystemParam)]
60pub struct SpatialQuery<'w, 's> {
61    colliders: Query<'w, 's, (&'static Position, &'static Rotation, &'static Collider)>,
62    aabbs: Query<'w, 's, &'static ColliderAabb>,
63    collider_trees: Res<'w, ColliderTrees>,
64    #[cfg(feature = "debug-plugin")]
65    tracked_queries: Option<Res<'w, TrackedSpatialQueries>>,
66}
67
68impl SpatialQuery<'_, '_> {
69    /// Records a spatial query for debug rendering if tracking is enabled.
70    #[cfg(feature = "debug-plugin")]
71    #[inline]
72    fn track(&self, query: impl FnOnce() -> TrackedSpatialQuery) {
73        if let Some(tracked_queries) = &self.tracked_queries
74            && tracked_queries.enabled
75        {
76            tracked_queries.borrow_local_mut().push(query());
77        }
78    }
79
80    /// Casts a [ray](spatial_query#raycasting) and computes the closest [hit](RayHitData) with a collider.
81    /// If there are no hits, `None` is returned.
82    ///
83    /// # Arguments
84    ///
85    /// - `origin`: Where the ray is cast from.
86    /// - `direction`: What direction the ray is cast in.
87    /// - `max_distance`: The maximum distance the ray can travel.
88    /// - `solid`: If true *and* the ray origin is inside of a collider, the hit point will be the ray origin itself.
89    ///   Otherwise, the collider will be treated as hollow, and the hit point will be at its boundary.
90    /// - `filter`: A [`SpatialQueryFilter`] that determines which entities are included in the cast.
91    ///
92    /// # Example
93    ///
94    /// ```
95    /// # #[cfg(feature = "2d")]
96    /// # use avian2d::prelude::*;
97    /// # #[cfg(feature = "3d")]
98    /// use avian3d::{math::RVec3, prelude::*};
99    /// use bevy::prelude::*;
100    ///
101    /// # #[cfg(feature = "3d")]
102    /// fn print_hits(spatial_query: SpatialQuery) {
103    ///     // Ray origin and direction
104    ///     let origin = RVec3::ZERO;
105    ///     let direction = Dir3::X;
106    ///
107    ///     // Configuration for the ray cast
108    ///     let max_distance = 100.0;
109    ///     let solid = true;
110    ///     let filter = SpatialQueryFilter::default();
111    ///
112    ///     // Cast ray and print first hit
113    ///     if let Some(first_hit) = spatial_query.cast_ray(origin, direction, max_distance, solid, &filter) {
114    ///         println!("First hit: {:?}", first_hit);
115    ///     }
116    /// }
117    /// ```
118    ///
119    /// # Related Methods
120    ///
121    /// - [`SpatialQuery::cast_ray_predicate`]
122    /// - [`SpatialQuery::ray_hits`]
123    /// - [`SpatialQuery::ray_hits_callback`]
124    pub fn cast_ray(
125        &self,
126        origin: RVector,
127        direction: Dir,
128        max_distance: f32,
129        solid: bool,
130        filter: &SpatialQueryFilter,
131    ) -> Option<RayHitData> {
132        let out =
133            self.cast_ray_predicate(origin, direction, max_distance, solid, filter, &|_| true);
134
135        #[cfg(feature = "debug-plugin")]
136        self.track(|| TrackedSpatialQuery::Raycast {
137            origin,
138            direction,
139            max_distance,
140            hits: out.iter().cloned().collect(),
141        });
142
143        out
144    }
145
146    /// Casts a [ray](spatial_query#raycasting) and computes the closest [hit](RayHitData) with a collider.
147    /// If there are no hits, `None` is returned.
148    ///
149    /// # Arguments
150    ///
151    /// - `origin`: Where the ray is cast from.
152    /// - `direction`: What direction the ray is cast in.
153    /// - `max_distance`: The maximum distance the ray can travel.
154    /// - `solid`: If true *and* the ray origin is inside of a collider, the hit point will be the ray origin itself.
155    ///   Otherwise, the collider will be treated as hollow, and the hit point will be at its boundary.
156    /// - `filter`: A [`SpatialQueryFilter`] that determines which entities are included in the cast.
157    /// - `predicate`: A function called on each entity hit by the ray. The ray keeps travelling until the predicate returns `false`.
158    ///
159    /// # Example
160    ///
161    /// ```
162    /// # #[cfg(feature = "2d")]
163    /// # use avian2d::prelude::*;
164    /// # #[cfg(feature = "3d")]
165    /// use avian3d::{math::RVec3, prelude::*};
166    /// use bevy::prelude::*;
167    ///
168    /// #[derive(Component)]
169    /// struct Invisible;
170    ///
171    /// # #[cfg(feature = "3d")]
172    /// fn print_hits(spatial_query: SpatialQuery, query: Query<&Invisible>) {
173    ///     // Ray origin and direction
174    ///     let origin = RVec3::ZERO;
175    ///     let direction = Dir3::X;
176    ///
177    ///     // Configuration for the ray cast
178    ///     let max_distance = 100.0;
179    ///     let solid = true;
180    ///     let filter = SpatialQueryFilter::default();
181    ///
182    ///     // Cast ray and get the first hit that matches the predicate
183    ///     let hit = spatial_query.cast_ray_predicate(origin, direction, max_distance, solid, &filter, &|entity| {
184    ///         // Skip entities with the `Invisible` component.
185    ///         !query.contains(entity)
186    ///     });
187    ///
188    ///     // Print first hit
189    ///     if let Some(first_hit) = hit {
190    ///         println!("First hit: {:?}", first_hit);
191    ///     }
192    /// }
193    /// ```
194    ///
195    /// # Related Methods
196    ///
197    /// - [`SpatialQuery::cast_ray`]
198    /// - [`SpatialQuery::ray_hits`]
199    /// - [`SpatialQuery::ray_hits_callback`]
200    pub fn cast_ray_predicate(
201        &self,
202        origin: RVector,
203        direction: Dir,
204        mut max_distance: f32,
205        solid: bool,
206        filter: &SpatialQueryFilter,
207        predicate: &dyn Fn(Entity) -> bool,
208    ) -> Option<RayHitData> {
209        let ray = Ray::new(origin.f32(), direction);
210
211        let mut closest_hit: Option<RayHitData> = None;
212
213        self.collider_trees.iter_trees().for_each(|tree| {
214            tree.ray_traverse_closest(ray, max_distance, |proxy_id| {
215                let proxy = tree.get_proxy(proxy_id).unwrap();
216                if !filter.test(proxy.collider, proxy.layers) || !predicate(proxy.collider) {
217                    return f32::MAX;
218                }
219
220                let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider) else {
221                    return f32::MAX;
222                };
223
224                let Some((distance, normal)) = collider.cast_ray(
225                    position.0,
226                    *rotation,
227                    origin,
228                    *direction,
229                    max_distance,
230                    solid,
231                ) else {
232                    return f32::MAX;
233                };
234
235                if distance < max_distance {
236                    max_distance = distance;
237                    closest_hit = Some(RayHitData {
238                        entity: proxy.collider,
239                        normal,
240                        distance,
241                    });
242                }
243
244                distance
245            });
246        });
247
248        closest_hit
249    }
250
251    /// Casts a [ray](spatial_query#raycasting) and computes all [hits](RayHitData) until `max_hits` is reached.
252    ///
253    /// Note that the order of the results is not guaranteed, and if there are more hits than `max_hits`,
254    /// some hits will be missed.
255    ///
256    /// # Arguments
257    ///
258    /// - `origin`: Where the ray is cast from.
259    /// - `direction`: What direction the ray is cast in.
260    /// - `max_distance`: The maximum distance the ray can travel.
261    /// - `max_hits`: The maximum number of hits. Additional hits will be missed.
262    /// - `solid`: If true *and* the ray origin is inside of a collider, the hit point will be the ray origin itself.
263    ///   Otherwise, the collider will be treated as hollow, and the hit point will be at its boundary.
264    /// - `filter`: A [`SpatialQueryFilter`] that determines which entities are included in the cast.
265    ///
266    /// # Example
267    ///
268    /// ```
269    /// # #[cfg(feature = "2d")]
270    /// # use avian2d::prelude::*;
271    /// # #[cfg(feature = "3d")]
272    /// use avian3d::{math::RVec3, prelude::*};
273    /// use bevy::prelude::*;
274    ///
275    /// # #[cfg(feature = "3d")]
276    /// fn print_hits(spatial_query: SpatialQuery) {
277    ///     // Ray origin and direction
278    ///     let origin = RVec3::ZERO;
279    ///     let direction = Dir3::X;
280    ///
281    ///     // Configuration for the ray cast
282    ///     let max_distance = 100.0;
283    ///     let solid = true;
284    ///     let filter = SpatialQueryFilter::default();
285    ///
286    ///     // Cast ray and get up to 20 hits
287    ///     let hits = spatial_query.ray_hits(origin, direction, max_distance, 20, solid, &filter);
288    ///
289    ///     // Print hits
290    ///     for hit in hits.iter() {
291    ///         println!("Hit: {:?}", hit);
292    ///     }
293    /// }
294    /// ```
295    ///
296    /// # Related Methods
297    ///
298    /// - [`SpatialQuery::cast_ray`]
299    /// - [`SpatialQuery::cast_ray_predicate`]
300    /// - [`SpatialQuery::ray_hits_callback`]
301    pub fn ray_hits(
302        &self,
303        origin: RVector,
304        direction: Dir,
305        max_distance: f32,
306        max_hits: u32,
307        solid: bool,
308        filter: &SpatialQueryFilter,
309    ) -> Vec<RayHitData> {
310        let mut hits = Vec::new();
311
312        self.ray_hits_callback(origin, direction, max_distance, solid, filter, |hit| {
313            if hits.len() < max_hits as usize {
314                hits.push(hit);
315                true
316            } else {
317                false
318            }
319        });
320
321        #[cfg(feature = "debug-plugin")]
322        self.track(|| TrackedSpatialQuery::Raycast {
323            origin,
324            direction,
325            max_distance,
326            hits: hits.clone(),
327        });
328
329        hits
330    }
331
332    /// Casts a [ray](spatial_query#raycasting) and computes all [hits](RayHitData), calling the given `callback`
333    /// for each hit. The raycast stops when `callback` returns false or all hits have been found.
334    ///
335    /// Note that the order of the results is not guaranteed.
336    ///
337    /// # Arguments
338    ///
339    /// - `origin`: Where the ray is cast from.
340    /// - `direction`: What direction the ray is cast in.
341    /// - `max_distance`: The maximum distance the ray can travel.
342    /// - `solid`: If true *and* the ray origin is inside of a collider, the hit point will be the ray origin itself.
343    ///   Otherwise, the collider will be treated as hollow, and the hit point will be at its boundary.
344    /// - `filter`: A [`SpatialQueryFilter`] that determines which entities are included in the cast.
345    /// - `callback`: A callback function called for each hit.
346    ///
347    /// # Example
348    ///
349    /// ```
350    /// # #[cfg(feature = "2d")]
351    /// # use avian2d::prelude::*;
352    /// # #[cfg(feature = "3d")]
353    /// use avian3d::{math::RVec3, prelude::*};
354    /// use bevy::prelude::*;
355    ///
356    /// # #[cfg(feature = "3d")]
357    /// fn print_hits(spatial_query: SpatialQuery) {
358    ///     // Ray origin and direction
359    ///     let origin = RVec3::ZERO;
360    ///     let direction = Dir3::X;
361    ///
362    ///     // Configuration for the ray cast
363    ///     let max_distance = 100.0;
364    ///     let solid = true;
365    ///     let filter = SpatialQueryFilter::default();
366    ///
367    ///     // Cast ray and get up to 20 hits
368    ///     let mut hits = vec![];
369    ///     spatial_query.ray_hits_callback(origin, direction, max_distance, solid, &filter, |hit| {
370    ///         hits.push(hit);
371    ///         hits.len() < 20
372    ///     });
373    ///
374    ///     // Print hits
375    ///     for hit in hits.iter() {
376    ///         println!("Hit: {:?}", hit);
377    ///     }
378    /// }
379    /// ```
380    ///
381    /// # Related Methods
382    ///
383    /// - [`SpatialQuery::cast_ray`]
384    /// - [`SpatialQuery::cast_ray_predicate`]
385    /// - [`SpatialQuery::ray_hits`]
386    pub fn ray_hits_callback(
387        &self,
388        origin: RVector,
389        direction: Dir,
390        max_distance: f32,
391        solid: bool,
392        filter: &SpatialQueryFilter,
393        mut callback: impl FnMut(RayHitData) -> bool,
394    ) {
395        let ray = Ray::new(origin.f32(), direction);
396
397        self.collider_trees.iter_trees().for_each(|tree| {
398            tree.ray_traverse_all(ray, max_distance, |proxy_id| {
399                let proxy = tree.get_proxy(proxy_id).unwrap();
400
401                if !filter.test(proxy.collider, proxy.layers) {
402                    return true;
403                }
404
405                let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider) else {
406                    return true;
407                };
408
409                let Some((distance, normal)) = collider.cast_ray(
410                    position.0,
411                    *rotation,
412                    origin,
413                    *direction,
414                    max_distance,
415                    solid,
416                ) else {
417                    return true;
418                };
419
420                callback(RayHitData {
421                    entity: proxy.collider,
422                    normal,
423                    distance,
424                })
425            });
426        });
427    }
428
429    /// Casts a [shape](spatial_query#shapecasting) with a given rotation and computes the closest [hit](ShapeHitData)
430    /// with a collider. If there are no hits, `None` is returned.
431    ///
432    /// For a more ECS-based approach, consider using the [`ShapeCaster`] component instead.
433    ///
434    /// # Arguments
435    ///
436    /// - `shape`: The shape being cast represented as a [`Collider`].
437    /// - `origin`: Where the shape is cast from.
438    /// - `shape_rotation`: The rotation of the shape being cast.
439    /// - `direction`: What direction the shape is cast in.
440    /// - `config`: A [`ShapeCastConfig`] that determines the behavior of the cast.
441    /// - `filter`: A [`SpatialQueryFilter`] that determines which entities are included in the cast.
442    ///
443    /// # Example
444    ///
445    /// ```
446    /// # #[cfg(feature = "2d")]
447    /// # use avian2d::prelude::*;
448    /// # #[cfg(feature = "3d")]
449    /// use avian3d::{math::RVec3, prelude::*};
450    /// use bevy::prelude::*;
451    ///
452    /// # #[cfg(feature = "3d")]
453    /// fn print_hits(spatial_query: SpatialQuery) {
454    ///     // Shape properties
455    ///     let shape = Collider::sphere(0.5);
456    ///     let origin = RVec3::ZERO;
457    ///     let rotation = Quat::default();
458    ///     let direction = Dir3::X;
459    ///
460    ///     // Configuration for the shape cast
461    ///     let config = ShapeCastConfig::from_max_distance(100.0);
462    ///     let filter = SpatialQueryFilter::default();
463    ///
464    ///     // Cast shape and print first hit
465    ///     if let Some(first_hit) = spatial_query.cast_shape(&shape, origin, rotation, direction, &config, &filter)
466    ///     {
467    ///         println!("First hit: {:?}", first_hit);
468    ///     }
469    /// }
470    /// ```
471    ///
472    /// # Related Methods
473    ///
474    /// - [`SpatialQuery::cast_shape_predicate`]
475    /// - [`SpatialQuery::shape_hits`]
476    /// - [`SpatialQuery::shape_hits_callback`]
477    #[allow(clippy::too_many_arguments)]
478    pub fn cast_shape(
479        &self,
480        shape: &Collider,
481        origin: RVector,
482        shape_rotation: impl Into<Rot>,
483        direction: Dir,
484        config: &ShapeCastConfig,
485        filter: &SpatialQueryFilter,
486    ) -> Option<ShapeHitData> {
487        #[cfg(feature = "debug-plugin")]
488        let shape_rotation = shape_rotation.into();
489
490        let out = self.cast_shape_predicate(
491            shape,
492            origin,
493            shape_rotation,
494            direction,
495            config,
496            filter,
497            &|_| true,
498        );
499
500        #[cfg(feature = "debug-plugin")]
501        self.track(|| TrackedSpatialQuery::Shapecast {
502            shape: shape.clone(),
503            origin,
504            rotation: shape_rotation,
505            direction,
506            max_distance: config.max_distance,
507            hits: out.iter().cloned().collect(),
508        });
509
510        out
511    }
512
513    /// Casts a [shape](spatial_query#shapecasting) with a given rotation and computes the closest [hit](ShapeHitData)
514    /// with a collider. If there are no hits, `None` is returned.
515    ///
516    /// For a more ECS-based approach, consider using the [`ShapeCaster`] component instead.
517    ///
518    /// # Arguments
519    ///
520    /// - `shape`: The shape being cast represented as a [`Collider`].
521    /// - `origin`: Where the shape is cast from.
522    /// - `shape_rotation`: The rotation of the shape being cast.
523    /// - `direction`: What direction the shape is cast in.
524    /// - `config`: A [`ShapeCastConfig`] that determines the behavior of the cast.
525    /// - `filter`: A [`SpatialQueryFilter`] that determines which entities are included in the cast.
526    /// - `predicate`: A function called on each entity hit by the shape. The shape keeps travelling until the predicate returns `false`.
527    ///
528    /// # Example
529    ///
530    /// ```
531    /// # #[cfg(feature = "2d")]
532    /// # use avian2d::prelude::*;
533    /// # #[cfg(feature = "3d")]
534    /// use avian3d::{math::RVec3, prelude::*};
535    /// use bevy::prelude::*;
536    ///
537    /// #[derive(Component)]
538    /// struct Invisible;
539    ///
540    /// # #[cfg(feature = "3d")]
541    /// fn print_hits(spatial_query: SpatialQuery, query: Query<&Invisible>) {
542    ///     // Shape properties
543    ///     let shape = Collider::sphere(0.5);
544    ///     let origin = RVec3::ZERO;
545    ///     let rotation = Quat::default();
546    ///     let direction = Dir3::X;
547    ///
548    ///     // Configuration for the shape cast
549    ///     let config = ShapeCastConfig::from_max_distance(100.0);
550    ///     let filter = SpatialQueryFilter::default();
551    ///
552    ///     // Cast shape and get the first hit that matches the predicate
553    ///     let hit = spatial_query.cast_shape_predicate(&shape, origin, rotation, direction, &config, &filter, &|entity| {
554    ///        // Skip entities with the `Invisible` component.
555    ///        !query.contains(entity)
556    ///     });
557    ///
558    ///     // Print first hit
559    ///     if let Some(first_hit) = hit {
560    ///         println!("First hit: {:?}", first_hit);
561    ///     }
562    /// }
563    /// ```
564    ///
565    /// # Related Methods
566    ///
567    /// - [`SpatialQuery::cast_ray`]
568    /// - [`SpatialQuery::ray_hits`]
569    /// - [`SpatialQuery::ray_hits_callback`]
570    pub fn cast_shape_predicate(
571        &self,
572        shape: &Collider,
573        origin: RVector,
574        shape_rotation: impl Into<Rot>,
575        direction: Dir,
576        config: &ShapeCastConfig,
577        filter: &SpatialQueryFilter,
578        predicate: &dyn Fn(Entity) -> bool,
579    ) -> Option<ShapeHitData> {
580        let shape_rotation = shape_rotation.into();
581
582        let mut closest_distance = config.max_distance;
583        let mut closest_hit: Option<ShapeHitData> = None;
584
585        let aabb = obvhs::aabb::Aabb::from(shape.aabb(origin, shape_rotation, 0.0));
586
587        self.collider_trees.iter_trees().for_each(|tree| {
588            tree.sweep_traverse_closest(
589                aabb,
590                direction,
591                closest_distance,
592                config.target_distance,
593                |proxy_id| {
594                    let proxy = tree.get_proxy(proxy_id).unwrap();
595
596                    if !filter.test(proxy.collider, proxy.layers) || !predicate(proxy.collider) {
597                        return f32::MAX;
598                    }
599
600                    let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider)
601                    else {
602                        return f32::MAX;
603                    };
604
605                    let pose1 = make_pose(position.0, *rotation);
606                    let pose2 = make_pose(origin, shape_rotation);
607
608                    let Ok(Some(hit)) = parry::query::cast_shapes(
609                        &pose1,
610                        RVector::ZERO,
611                        collider.shape_scaled().as_ref(),
612                        &pose2,
613                        direction.real(),
614                        shape.shape_scaled().as_ref(),
615                        ShapeCastOptions {
616                            max_time_of_impact: config.max_distance.real(),
617                            target_distance: config.target_distance.real(),
618                            stop_at_penetration: !config.ignore_origin_penetration,
619                            compute_impact_geometry_on_penetration: config
620                                .compute_contact_on_penetration,
621                        },
622                    ) else {
623                        return f32::MAX;
624                    };
625
626                    let toi = hit.time_of_impact.f32();
627                    if toi < closest_distance {
628                        closest_distance = toi;
629                        closest_hit = Some(ShapeHitData {
630                            entity: proxy.collider,
631                            point1: pose1 * hit.witness1,
632                            point2: pose2 * hit.witness2 + (direction * toi).real(),
633                            normal1: (pose1.rotation * hit.normal1).f32(),
634                            normal2: (pose2.rotation * hit.normal2).f32(),
635                            distance: toi,
636                        });
637                    }
638                    toi
639                },
640            );
641        });
642
643        closest_hit
644    }
645
646    /// Casts a [shape](spatial_query#shapecasting) with a given rotation and computes computes all [hits](ShapeHitData)
647    /// in the order of distance until `max_hits` is reached.
648    ///
649    /// Note that the order of the results is not guaranteed, and if there are more hits than `max_hits`,
650    /// some hits will be missed.
651    ///
652    /// # Arguments
653    ///
654    /// - `shape`: The shape being cast represented as a [`Collider`].
655    /// - `origin`: Where the shape is cast from.
656    /// - `shape_rotation`: The rotation of the shape being cast.
657    /// - `direction`: What direction the shape is cast in.
658    /// - `max_hits`: The maximum number of hits. Additional hits will be missed.
659    /// - `config`: A [`ShapeCastConfig`] that determines the behavior of the cast.
660    /// - `filter`: A [`SpatialQueryFilter`] that determines which entities are included in the cast.
661    /// - `callback`: A callback function called for each hit.
662    ///
663    /// # Example
664    ///
665    /// ```
666    /// # #[cfg(feature = "2d")]
667    /// # use avian2d::prelude::*;
668    /// # #[cfg(feature = "3d")]
669    /// use avian3d::{math::RVec3, prelude::*};
670    /// use bevy::prelude::*;
671    ///
672    /// # #[cfg(feature = "3d")]
673    /// fn print_hits(spatial_query: SpatialQuery) {
674    ///     // Shape properties
675    ///     let shape = Collider::sphere(0.5);
676    ///     let origin = RVec3::ZERO;
677    ///     let rotation = Quat::default();
678    ///     let direction = Dir3::X;
679    ///
680    ///     // Configuration for the shape cast
681    ///     let config = ShapeCastConfig::from_max_distance(100.0);
682    ///     let filter = SpatialQueryFilter::default();
683    ///
684    ///     // Cast shape and get up to 20 hits
685    ///     let hits = spatial_query.shape_hits(&shape, origin, rotation, direction, 20, &config, &filter);
686    ///
687    ///     // Print hits
688    ///     for hit in hits.iter() {
689    ///         println!("Hit: {:?}", hit);
690    ///     }
691    /// }
692    /// ```
693    ///
694    /// # Related Methods
695    ///
696    /// - [`SpatialQuery::cast_shape`]
697    /// - [`SpatialQuery::cast_shape_predicate`]
698    /// - [`SpatialQuery::shape_hits_callback`]
699    #[allow(clippy::too_many_arguments)]
700    pub fn shape_hits(
701        &self,
702        shape: &Collider,
703        origin: RVector,
704        shape_rotation: impl Into<Rot>,
705        direction: Dir,
706        max_hits: u32,
707        config: &ShapeCastConfig,
708        filter: &SpatialQueryFilter,
709    ) -> Vec<ShapeHitData> {
710        let mut hits = Vec::new();
711        #[cfg(feature = "debug-plugin")]
712        let shape_rotation = shape_rotation.into();
713
714        self.shape_hits_callback(
715            shape,
716            origin,
717            shape_rotation,
718            direction,
719            config,
720            filter,
721            |hit| {
722                if hits.len() < max_hits as usize {
723                    hits.push(hit);
724                    true
725                } else {
726                    false
727                }
728            },
729        );
730
731        #[cfg(feature = "debug-plugin")]
732        self.track(|| TrackedSpatialQuery::Shapecast {
733            shape: shape.clone(),
734            origin,
735            rotation: shape_rotation,
736            direction,
737            max_distance: config.max_distance,
738            hits: hits.clone(),
739        });
740
741        hits
742    }
743
744    /// Casts a [shape](spatial_query#shapecasting) with a given rotation and computes computes all [hits](ShapeHitData)
745    /// in the order of distance, calling the given `callback` for each hit. The shapecast stops when
746    /// `callback` returns false or all hits have been found.
747    ///
748    /// Note that the order of the results is not guaranteed.
749    ///
750    /// # Arguments
751    ///
752    /// - `shape`: The shape being cast represented as a [`Collider`].
753    /// - `origin`: Where the shape is cast from.
754    /// - `shape_rotation`: The rotation of the shape being cast.
755    /// - `direction`: What direction the shape is cast in.
756    /// - `config`: A [`ShapeCastConfig`] that determines the behavior of the cast.
757    /// - `filter`: A [`SpatialQueryFilter`] that determines which entities are included in the cast.
758    /// - `callback`: A callback function called for each hit.
759    ///
760    /// # Example
761    ///
762    /// ```
763    /// # #[cfg(feature = "2d")]
764    /// # use avian2d::prelude::*;
765    /// # #[cfg(feature = "3d")]
766    /// use avian3d::{math::RVec3, prelude::*};
767    /// use bevy::prelude::*;
768    ///
769    /// # #[cfg(feature = "3d")]
770    /// fn print_hits(spatial_query: SpatialQuery) {
771    ///     // Shape properties
772    ///     let shape = Collider::sphere(0.5);
773    ///     let origin = RVec3::ZERO;
774    ///     let rotation = Quat::default();
775    ///     let direction = Dir3::X;
776    ///
777    ///     // Configuration for the shape cast
778    ///     let config = ShapeCastConfig::from_max_distance(100.0);
779    ///     let filter = SpatialQueryFilter::default();
780    ///
781    ///     // Cast shape and get up to 20 hits
782    ///     let mut hits = vec![];
783    ///     spatial_query.shape_hits_callback(&shape, origin, rotation, direction, &config, &filter, |hit| {
784    ///         hits.push(hit);
785    ///         hits.len() < 20
786    ///     });
787    ///
788    ///     // Print hits
789    ///     for hit in hits.iter() {
790    ///         println!("Hit: {:?}", hit);
791    ///     }
792    /// }
793    /// ```
794    ///
795    /// # Related Methods
796    ///
797    /// - [`SpatialQuery::cast_shape`]
798    /// - [`SpatialQuery::cast_shape_predicate`]
799    /// - [`SpatialQuery::shape_hits`]
800    #[allow(clippy::too_many_arguments)]
801    pub fn shape_hits_callback(
802        &self,
803        shape: &Collider,
804        origin: RVector,
805        shape_rotation: impl Into<Rot>,
806        direction: Dir,
807        config: &ShapeCastConfig,
808        filter: &SpatialQueryFilter,
809        mut callback: impl FnMut(ShapeHitData) -> bool,
810    ) {
811        let shape_rotation = shape_rotation.into();
812
813        let aabb = obvhs::aabb::Aabb::from(shape.aabb(origin, shape_rotation, 0.0));
814
815        self.collider_trees.iter_trees().for_each(|tree| {
816            tree.sweep_traverse_all(
817                aabb,
818                direction,
819                config.max_distance,
820                config.target_distance,
821                |proxy_id| {
822                    let proxy = tree.get_proxy(proxy_id).unwrap();
823
824                    if !filter.test(proxy.collider, proxy.layers) {
825                        return true;
826                    }
827
828                    let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider)
829                    else {
830                        return true;
831                    };
832
833                    let pose1 = make_pose(position.0, *rotation);
834                    let pose2 = make_pose(origin, shape_rotation);
835
836                    let Ok(Some(hit)) = parry::query::cast_shapes(
837                        &pose1,
838                        RVector::ZERO,
839                        collider.shape_scaled().as_ref(),
840                        &pose2,
841                        direction.real(),
842                        shape.shape_scaled().as_ref(),
843                        ShapeCastOptions {
844                            max_time_of_impact: config.max_distance.real(),
845                            target_distance: config.target_distance.real(),
846                            stop_at_penetration: !config.ignore_origin_penetration,
847                            compute_impact_geometry_on_penetration: config
848                                .compute_contact_on_penetration,
849                        },
850                    ) else {
851                        return true;
852                    };
853
854                    let toi = hit.time_of_impact.f32();
855                    callback(ShapeHitData {
856                        entity: proxy.collider,
857                        point1: position.0 + rotation.real() * hit.witness1,
858                        point2: pose2 * hit.witness2 + (direction * toi).real(),
859                        normal1: (pose1.rotation * hit.normal1).f32(),
860                        normal2: (pose2.rotation * hit.normal2).f32(),
861                        distance: toi,
862                    })
863                },
864            );
865        });
866    }
867
868    /// Finds the [projection](spatial_query#point-projection) of a given point on the closest [collider](Collider).
869    /// If one isn't found, `None` is returned.
870    ///
871    /// # Arguments
872    ///
873    /// - `point`: The point that should be projected.
874    /// - `solid`: If true and the point is inside of a collider, the projection will be at the point.
875    ///   Otherwise, the collider will be treated as hollow, and the projection will be at the collider's boundary.
876    /// - `query_filter`: A [`SpatialQueryFilter`] that determines which colliders are taken into account in the query.
877    ///
878    /// # Example
879    ///
880    /// ```
881    /// # #[cfg(feature = "2d")]
882    /// # use avian2d::prelude::*;
883    /// # #[cfg(feature = "3d")]
884    /// use avian3d::{math::RVec3, prelude::*};
885    /// use bevy::prelude::*;
886    ///
887    /// # #[cfg(feature = "3d")]
888    /// fn print_point_projection(spatial_query: SpatialQuery) {
889    ///     // Project a point and print the result
890    ///     if let Some(projection) = spatial_query.project_point(
891    ///         RVec3::ZERO,                   // Point
892    ///         true,                          // Are colliders treated as "solid"
893    ///         &SpatialQueryFilter::default(),// Query filter
894    ///     ) {
895    ///         println!("Projection: {:?}", projection);
896    ///     }
897    /// }
898    /// ```
899    ///
900    /// # Related Methods
901    ///
902    /// - [`SpatialQuery::project_point_predicate`]
903    pub fn project_point(
904        &self,
905        point: RVector,
906        solid: bool,
907        filter: &SpatialQueryFilter,
908    ) -> Option<PointProjection> {
909        let out = self.project_point_predicate(point, solid, filter, &|_| true);
910
911        #[cfg(feature = "debug-plugin")]
912        if let Some(projection) = &out {
913            self.track(|| TrackedSpatialQuery::PointProjection {
914                point,
915                projection: projection.point,
916            });
917        }
918
919        out
920    }
921
922    /// Finds the [projection](spatial_query#point-projection) of a given point on the closest [collider](Collider).
923    /// If one isn't found, `None` is returned.
924    ///
925    /// # Arguments
926    ///
927    /// - `point`: The point that should be projected.
928    /// - `solid`: If true and the point is inside of a collider, the projection will be at the point.
929    ///   Otherwise, the collider will be treated as hollow, and the projection will be at the collider's boundary.
930    /// - `filter`: A [`SpatialQueryFilter`] that determines which colliders are taken into account in the query.
931    /// - `predicate`: A function for filtering which entities are considered in the query. The projection will be on the closest collider that passes the predicate.
932    ///
933    /// # Example
934    ///
935    /// ```
936    /// # #[cfg(feature = "2d")]
937    /// # use avian2d::prelude::*;
938    /// # #[cfg(feature = "3d")]
939    /// use avian3d::{math::RVec3, prelude::*};
940    /// use bevy::prelude::*;
941    ///
942    /// #[derive(Component)]
943    /// struct Invisible;
944    ///
945    /// # #[cfg(feature = "3d")]
946    /// fn print_point_projection(spatial_query: SpatialQuery, query: Query<&Invisible>) {
947    ///     // Project a point and print the result
948    ///     if let Some(projection) = spatial_query.project_point_predicate(
949    ///         RVec3::ZERO,                    // Point
950    ///         true,                           // Are colliders treated as "solid"
951    ///         &SpatialQueryFilter::default(), // Query filter
952    ///         &|entity| {                     // Predicate
953    ///             // Skip entities with the `Invisible` component.
954    ///             !query.contains(entity)
955    ///         }
956    ///     ) {
957    ///         println!("Projection: {:?}", projection);
958    ///     }
959    /// }
960    /// ```
961    ///
962    /// # Related Methods
963    ///
964    /// - [`SpatialQuery::project_point`]
965    pub fn project_point_predicate(
966        &self,
967        point: RVector,
968        solid: bool,
969        filter: &SpatialQueryFilter,
970        predicate: &dyn Fn(Entity) -> bool,
971    ) -> Option<PointProjection> {
972        let mut closest_distance_squared = f32::INFINITY;
973        let mut closest_projection: Option<PointProjection> = None;
974
975        self.collider_trees.iter_trees().for_each(|tree| {
976            tree.squared_distance_traverse_closest(point, f32::INFINITY, |proxy_id| {
977                let proxy = tree.get_proxy(proxy_id).unwrap();
978                if !filter.test(proxy.collider, proxy.layers) || !predicate(proxy.collider) {
979                    return f32::INFINITY;
980                }
981
982                let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider) else {
983                    return f32::INFINITY;
984                };
985
986                let (projection, is_inside) =
987                    collider.project_point(position.0, *rotation, point, solid);
988
989                let distance_squared = (projection - point).length_squared().f32();
990                if distance_squared < closest_distance_squared {
991                    closest_distance_squared = distance_squared;
992                    closest_projection = Some(PointProjection {
993                        entity: proxy.collider,
994                        point: projection,
995                        is_inside,
996                    });
997                }
998                distance_squared
999            });
1000        });
1001
1002        closest_projection
1003    }
1004
1005    /// An [intersection test](spatial_query#intersection-tests) that finds all entities with a [collider](Collider)
1006    /// that contains the given point.
1007    ///
1008    /// # Arguments
1009    ///
1010    /// - `point`: The point that intersections are tested against.
1011    /// - `filter`: A [`SpatialQueryFilter`] that determines which colliders are taken into account in the query.
1012    ///
1013    /// # Example
1014    ///
1015    /// ```
1016    /// # #[cfg(feature = "2d")]
1017    /// # use avian2d::prelude::*;
1018    /// # #[cfg(feature = "3d")]
1019    /// use avian3d::{math::RVec3, prelude::*};
1020    /// use bevy::prelude::*;
1021    ///
1022    /// # #[cfg(feature = "3d")]
1023    /// fn print_point_intersections(spatial_query: SpatialQuery) {
1024    ///     let intersections =
1025    ///         spatial_query.point_intersections(RVec3::ZERO, &SpatialQueryFilter::default());
1026    ///
1027    ///     for entity in intersections.iter() {
1028    ///         println!("Entity: {}", entity);
1029    ///     }
1030    /// }
1031    /// ```
1032    ///
1033    /// # Related Methods
1034    ///
1035    /// - [`SpatialQuery::point_intersections_callback`]
1036    pub fn point_intersections(&self, point: RVector, filter: &SpatialQueryFilter) -> Vec<Entity> {
1037        let mut intersections = vec![];
1038
1039        self.point_intersections_callback(point, filter, |entity| {
1040            intersections.push(entity);
1041            true
1042        });
1043
1044        intersections
1045    }
1046
1047    /// An [intersection test](spatial_query#intersection-tests) that finds all entities with a [collider](Collider)
1048    /// that contains the given point, calling the given `callback` for each intersection.
1049    /// The search stops when `callback` returns `false` or all intersections have been found.
1050    ///
1051    /// # Arguments
1052    ///
1053    /// - `point`: The point that intersections are tested against.
1054    /// - `filter`: A [`SpatialQueryFilter`] that determines which colliders are taken into account in the query.
1055    /// - `callback`: A callback function called for each intersection.
1056    ///
1057    /// # Example
1058    ///
1059    /// ```
1060    /// # #[cfg(feature = "2d")]
1061    /// # use avian2d::prelude::*;
1062    /// # #[cfg(feature = "3d")]
1063    /// use avian3d::{math::RVec3, prelude::*};
1064    /// use bevy::prelude::*;
1065    ///
1066    /// # #[cfg(feature = "3d")]
1067    /// fn print_point_intersections(spatial_query: SpatialQuery) {
1068    ///     let mut intersections = vec![];
1069    ///     
1070    ///     spatial_query.point_intersections_callback(
1071    ///         RVec3::ZERO,                    // Point
1072    ///         &SpatialQueryFilter::default(), // Query filter
1073    ///         |entity| {                      // Callback function
1074    ///             intersections.push(entity);
1075    ///             true
1076    ///         },
1077    ///     );
1078    ///
1079    ///     for entity in intersections.iter() {
1080    ///         println!("Entity: {}", entity);
1081    ///     }
1082    /// }
1083    /// ```
1084    ///
1085    /// # Related Methods
1086    ///
1087    /// - [`SpatialQuery::point_intersections`]
1088    pub fn point_intersections_callback(
1089        &self,
1090        point: RVector,
1091        filter: &SpatialQueryFilter,
1092        mut callback: impl FnMut(Entity) -> bool,
1093    ) {
1094        self.collider_trees.iter_trees().for_each(|tree| {
1095            tree.point_traverse(point.f32(), |proxy_id| {
1096                let proxy = tree.get_proxy(proxy_id).unwrap();
1097
1098                if !filter.test(proxy.collider, proxy.layers) {
1099                    return true;
1100                }
1101
1102                let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider) else {
1103                    return true;
1104                };
1105
1106                if collider.contains_point(position.0, *rotation, point) {
1107                    callback(proxy.collider)
1108                } else {
1109                    true
1110                }
1111            });
1112        });
1113    }
1114
1115    /// An [intersection test](spatial_query#intersection-tests) that finds all entities with a [`ColliderAabb`]
1116    /// that is intersecting the given `aabb`.
1117    ///
1118    /// # Example
1119    ///
1120    /// ```
1121    /// # #[cfg(feature = "2d")]
1122    /// # use avian2d::prelude::*;
1123    /// # #[cfg(feature = "3d")]
1124    /// use avian3d::{math::RVec3, prelude::*};
1125    /// use bevy::prelude::*;
1126    ///
1127    /// # #[cfg(feature = "3d")]
1128    /// fn print_aabb_intersections(spatial_query: SpatialQuery) {
1129    ///     let aabb = Collider::sphere(0.5).aabb(RVec3::ZERO, Quat::default(), 0.0);
1130    ///     let intersections = spatial_query.aabb_intersections_with_aabb(aabb);
1131    ///
1132    ///     for entity in intersections.iter() {
1133    ///         println!("Entity: {}", entity);
1134    ///     }
1135    /// }
1136    /// ```
1137    ///
1138    /// # Related Methods
1139    ///
1140    /// - [`SpatialQuery::aabb_intersections_with_aabb_callback`]
1141    pub fn aabb_intersections_with_aabb(&self, aabb: ColliderAabb) -> Vec<Entity> {
1142        let mut intersections = vec![];
1143
1144        self.aabb_intersections_with_aabb_callback(aabb, |entity| {
1145            intersections.push(entity);
1146            true
1147        });
1148
1149        intersections
1150    }
1151
1152    /// An [intersection test](spatial_query#intersection-tests) that finds all entities with a [`ColliderAabb`]
1153    /// that is intersecting the given `aabb`, calling `callback` for each intersection.
1154    /// The search stops when `callback` returns `false` or all intersections have been found.
1155    ///
1156    /// # Example
1157    ///
1158    /// ```
1159    /// # #[cfg(feature = "2d")]
1160    /// # use avian2d::prelude::*;
1161    /// # #[cfg(feature = "3d")]
1162    /// use avian3d::{math::RVec3, prelude::*};
1163    /// use bevy::prelude::*;
1164    ///
1165    /// # #[cfg(feature = "3d")]
1166    /// fn print_aabb_intersections(spatial_query: SpatialQuery) {
1167    ///     let mut intersections = vec![];
1168    ///
1169    ///     spatial_query.aabb_intersections_with_aabb_callback(
1170    ///         Collider::sphere(0.5).aabb(RVec3::ZERO, Quat::default(), 0.0),
1171    ///         |entity| {
1172    ///             intersections.push(entity);
1173    ///             true
1174    ///         }
1175    ///     );
1176    ///
1177    ///     for entity in intersections.iter() {
1178    ///         println!("Entity: {}", entity);
1179    ///     }
1180    /// }
1181    /// ```
1182    ///
1183    /// # Related Methods
1184    ///
1185    /// - [`SpatialQuery::aabb_intersections_with_aabb`]
1186    pub fn aabb_intersections_with_aabb_callback(
1187        &self,
1188        aabb: ColliderAabb,
1189        mut callback: impl FnMut(Entity) -> bool,
1190    ) {
1191        self.collider_trees.iter_trees().for_each(|tree| {
1192            tree.aabb_traverse(obvhs::aabb::Aabb::from(aabb), |proxy_id| {
1193                let proxy = tree.get_proxy(proxy_id).unwrap();
1194                let Ok(proxy_aabb) = self.aabbs.get(proxy.collider) else {
1195                    return true;
1196                };
1197                // The proxy AABB is more tightly fitted to the collider than the AABB in the tree,
1198                // so we need to do an additional AABB intersection test here.
1199                if proxy_aabb.intersects(&aabb) {
1200                    callback(proxy.collider)
1201                } else {
1202                    true
1203                }
1204            });
1205        });
1206    }
1207
1208    /// An [intersection test](spatial_query#intersection-tests) that finds all entities with a [`Collider`]
1209    /// that is intersecting the given `shape` with a given position and rotation.
1210    ///
1211    /// # Arguments
1212    ///
1213    /// - `shape`: The shape that intersections are tested against represented as a [`Collider`].
1214    /// - `shape_position`: The position of the shape.
1215    /// - `shape_rotation`: The rotation of the shape.
1216    /// - `filter`: A [`SpatialQueryFilter`] that determines which colliders are taken into account in the query.
1217    ///
1218    /// # Example
1219    ///
1220    /// ```
1221    /// # #[cfg(feature = "2d")]
1222    /// # use avian2d::prelude::*;
1223    /// # #[cfg(feature = "3d")]
1224    /// use avian3d::{math::RVec3, prelude::*};
1225    /// use bevy::prelude::*;
1226    ///
1227    /// # #[cfg(feature = "3d")]
1228    /// fn print_shape_intersections(spatial_query: SpatialQuery) {
1229    ///     let intersections = spatial_query.shape_intersections(
1230    ///         &Collider::sphere(0.5),          // Shape
1231    ///         RVec3::ZERO,                     // Shape position
1232    ///         Quat::default(),                 // Shape rotation
1233    ///         &SpatialQueryFilter::default(),  // Query filter
1234    ///     );
1235    ///
1236    ///     for entity in intersections.iter() {
1237    ///         println!("Entity: {}", entity);
1238    ///     }
1239    /// }
1240    /// ```
1241    ///
1242    /// # Related Methods
1243    ///
1244    /// - [`SpatialQuery::shape_intersections_callback`]
1245    pub fn shape_intersections(
1246        &self,
1247        shape: &Collider,
1248        shape_position: RVector,
1249        shape_rotation: impl Into<Rot>,
1250        filter: &SpatialQueryFilter,
1251    ) -> Vec<Entity> {
1252        let mut intersections = vec![];
1253        #[cfg(feature = "debug-plugin")]
1254        let shape_rotation = shape_rotation.into();
1255
1256        self.shape_intersections_callback(
1257            shape,
1258            shape_position,
1259            shape_rotation,
1260            filter,
1261            |entity| {
1262                intersections.push(entity);
1263                true
1264            },
1265        );
1266
1267        #[cfg(feature = "debug-plugin")]
1268        self.track(|| TrackedSpatialQuery::ShapeIntersections {
1269            shape: shape.clone(),
1270            position: shape_position,
1271            rotation: shape_rotation,
1272            hits: intersections.clone(),
1273        });
1274
1275        intersections
1276    }
1277
1278    /// An [intersection test](spatial_query#intersection-tests) that finds all entities with a [`Collider`]
1279    /// that is intersecting the given `shape` with a given position and rotation, calling `callback` for each
1280    /// intersection. The search stops when `callback` returns `false` or all intersections have been found.
1281    ///
1282    /// # Arguments
1283    ///
1284    /// - `shape`: The shape that intersections are tested against represented as a [`Collider`].
1285    /// - `shape_position`: The position of the shape.
1286    /// - `shape_rotation`: The rotation of the shape.
1287    /// - `filter`: A [`SpatialQueryFilter`] that determines which colliders are taken into account in the query.
1288    /// - `callback`: A callback function called for each intersection.
1289    ///
1290    /// # Example
1291    ///
1292    /// ```
1293    /// # #[cfg(feature = "2d")]
1294    /// # use avian2d::prelude::*;
1295    /// # #[cfg(feature = "3d")]
1296    /// use avian3d::{math::RVec3, prelude::*};
1297    /// use bevy::prelude::*;
1298    ///
1299    /// # #[cfg(feature = "3d")]
1300    /// fn print_shape_intersections(spatial_query: SpatialQuery) {
1301    ///     let mut intersections = vec![];
1302    ///
1303    ///     spatial_query.shape_intersections_callback(
1304    ///         &Collider::sphere(0.5),          // Shape
1305    ///         RVec3::ZERO,                     // Shape position
1306    ///         Quat::default(),                 // Shape rotation
1307    ///         &SpatialQueryFilter::default(),  // Query filter
1308    ///         |entity| {                       // Callback function
1309    ///             intersections.push(entity);
1310    ///             true
1311    ///         },
1312    ///     );
1313    ///
1314    ///     for entity in intersections.iter() {
1315    ///         println!("Entity: {}", entity);
1316    ///     }
1317    /// }
1318    /// ```
1319    ///
1320    /// # Related Methods
1321    ///
1322    /// - [`SpatialQuery::shape_intersections`]
1323    pub fn shape_intersections_callback(
1324        &self,
1325        shape: &Collider,
1326        shape_position: RVector,
1327        shape_rotation: impl Into<Rot>,
1328        filter: &SpatialQueryFilter,
1329        mut callback: impl FnMut(Entity) -> bool,
1330    ) {
1331        let shape_rotation = shape_rotation.into();
1332
1333        let aabb = obvhs::aabb::Aabb::from(shape.aabb(shape_position, shape_rotation, 0.0));
1334
1335        self.collider_trees.iter_trees().for_each(|tree| {
1336            tree.aabb_traverse(aabb, |proxy_id| {
1337                let proxy = tree.get_proxy(proxy_id).unwrap();
1338                if !filter.test(proxy.collider, proxy.layers) {
1339                    return true;
1340                }
1341
1342                let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider) else {
1343                    return true;
1344                };
1345
1346                if contact_query::intersection_test(
1347                    collider,
1348                    position.0,
1349                    *rotation,
1350                    shape,
1351                    shape_position,
1352                    shape_rotation,
1353                )
1354                .is_ok_and(|intersects| intersects)
1355                {
1356                    callback(proxy.collider)
1357                } else {
1358                    true
1359                }
1360            });
1361        });
1362    }
1363}
1364
1365/// The result of a [point projection](spatial_query#point-projection) on a [collider](Collider).
1366#[derive(Clone, Debug, PartialEq, Reflect)]
1367#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1368#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
1369#[reflect(Debug, PartialEq)]
1370pub struct PointProjection {
1371    /// The entity of the collider that the point was projected onto.
1372    pub entity: Entity,
1373    /// The point where the point was projected.
1374    pub point: RVector,
1375    /// True if the point was inside of the collider.
1376    pub is_inside: bool,
1377}