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}