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