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}