avian3d/spatial_query/ray_caster.rs
1use crate::prelude::*;
2use bevy::{
3 ecs::{
4 entity::{EntityMapper, MapEntities},
5 lifecycle::HookContext,
6 world::DeferredWorld,
7 },
8 prelude::*,
9};
10
11/// A component used for [raycasting](spatial_query#raycasting).
12///
13/// **Raycasting** is a type of [spatial query](spatial_query) that finds one or more hits
14/// between a ray and a set of colliders.
15///
16/// Each ray is defined by a local `origin` and a `direction`. The [`RayCaster`] will find each hit
17/// and add them to the [`RayHits`] component. Each hit has a `distance` property which refers to
18/// how far the ray travelled, along with a `normal` for the point of intersection.
19///
20/// The [`RayCaster`] is the easiest way to handle simple raycasts. If you want more control and don't want to
21/// perform raycasts every frame, consider using the [`SpatialQuery`] system parameter.
22///
23/// # Hit Count and Order
24///
25/// The results of a raycast are in an arbitrary order by default. You can iterate over them in the order of
26/// distance with the [`RayHits::iter_sorted`] method.
27///
28/// You can configure the maximum amount of hits for a ray using `max_hits`. By default this is unbounded,
29/// so you will get all hits. When the number or complexity of colliders is large, this can be very
30/// expensive computationally. Set the value to whatever works best for your case.
31///
32/// Note that when there are more hits than `max_hits`, **some hits will be missed**.
33/// To guarantee that the closest hit is included, you should set `max_hits` to one or a value that
34/// is enough to contain all hits.
35///
36/// # Example
37///
38/// ```
39/// # #[cfg(feature = "2d")]
40/// # use avian2d::prelude::*;
41/// # #[cfg(feature = "3d")]
42/// use avian3d::prelude::*;
43/// use bevy::prelude::*;
44///
45/// # #[cfg(all(feature = "3d", feature = "f32"))]
46/// fn setup(mut commands: Commands) {
47/// // Spawn a ray at the center going right
48/// commands.spawn(RayCaster::new(Vec3::ZERO, Dir3::X));
49/// // ...spawn colliders and other things
50/// }
51///
52/// # #[cfg(all(feature = "3d", feature = "f32"))]
53/// fn print_hits(query: Query<(&RayCaster, &RayHits)>) {
54/// for (ray, hits) in &query {
55/// // For the faster iterator that isn't sorted, use `.iter()`
56/// for hit in hits.iter_sorted() {
57/// println!(
58/// "Hit entity {} at {} with normal {}",
59/// hit.entity,
60/// ray.get_global_point(hit.distance),
61/// hit.normal,
62/// );
63/// }
64/// }
65/// }
66/// ```
67#[derive(Component, Clone, Debug, PartialEq, Reflect)]
68#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
69#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
70#[reflect(Debug, Component, PartialEq)]
71#[component(on_add = on_add_ray_caster)]
72#[require(RayHits)]
73pub struct RayCaster {
74 /// Controls if the ray caster is enabled.
75 pub enabled: bool,
76
77 /// The local origin of the ray relative to the [`Position`] and [`Rotation`] of the ray entity or its parent.
78 ///
79 /// To get the global origin, use the `global_origin` method.
80 pub origin: Vector,
81
82 /// The global origin of the ray.
83 global_origin: Vector,
84
85 /// The local direction of the ray relative to the [`Rotation`] of the ray entity or its parent.
86 ///
87 /// To get the global direction, use the `global_direction` method.
88 pub direction: Dir,
89
90 /// The global direction of the ray.
91 global_direction: Dir,
92
93 /// The maximum number of hits allowed.
94 ///
95 /// When there are more hits than `max_hits`, **some hits will be missed**.
96 /// To guarantee that the closest hit is included, you should set `max_hits` to one or a value that
97 /// is enough to contain all hits.
98 pub max_hits: u32,
99
100 /// The maximum distance the ray can travel.
101 ///
102 /// By default this is infinite, so the ray will travel until all hits up to `max_hits` have been checked.
103 #[doc(alias = "max_time_of_impact")]
104 pub max_distance: Scalar,
105
106 /// Controls how the ray behaves when the ray origin is inside of a [collider](Collider).
107 ///
108 /// If `true`, shapes will be treated as solid, and the ray cast will return with a distance of `0.0`
109 /// if the ray origin is inside of the shape. Otherwise, shapes will be treated as hollow, and the ray
110 /// will always return a hit at the shape's boundary.
111 pub solid: bool,
112
113 /// If true, the ray caster ignores hits against its own [`Collider`]. This is the default.
114 pub ignore_self: bool,
115
116 /// Rules that determine which colliders are taken into account in the ray cast.
117 pub query_filter: SpatialQueryFilter,
118}
119
120impl Default for RayCaster {
121 fn default() -> Self {
122 Self {
123 enabled: true,
124 origin: Vector::ZERO,
125 global_origin: Vector::ZERO,
126 direction: Dir::X,
127 global_direction: Dir::X,
128 max_distance: Scalar::MAX,
129 max_hits: u32::MAX,
130 solid: true,
131 ignore_self: true,
132 query_filter: SpatialQueryFilter::default(),
133 }
134 }
135}
136
137impl From<Ray> for RayCaster {
138 fn from(ray: Ray) -> Self {
139 RayCaster::from_ray(ray)
140 }
141}
142
143impl RayCaster {
144 /// Creates a new [`RayCaster`] with a given origin and direction.
145 pub fn new(origin: Vector, direction: Dir) -> Self {
146 Self {
147 origin,
148 direction,
149 ..default()
150 }
151 }
152
153 /// Creates a new [`RayCaster`] from a ray.
154 pub fn from_ray(ray: Ray) -> Self {
155 Self {
156 origin: ray.origin.adjust_precision(),
157 direction: ray.direction,
158 ..default()
159 }
160 }
161
162 /// Sets the ray origin.
163 pub fn with_origin(mut self, origin: Vector) -> Self {
164 self.origin = origin;
165 self
166 }
167
168 /// Sets the ray direction.
169 pub fn with_direction(mut self, direction: Dir) -> Self {
170 self.direction = direction;
171 self
172 }
173
174 /// Controls how the ray behaves when the ray origin is inside of a [collider](Collider).
175 ///
176 /// If `true`, shapes will be treated as solid, and the ray cast will return with a distance of `0.0`
177 /// if the ray origin is inside of the shape. Otherwise, shapes will be treated as hollow, and the ray
178 /// will always return a hit at the shape's boundary.
179 pub fn with_solidness(mut self, solid: bool) -> Self {
180 self.solid = solid;
181 self
182 }
183
184 /// Sets if the ray caster should ignore hits against its own [`Collider`].
185 ///
186 /// The default is `true`.
187 pub fn with_ignore_self(mut self, ignore: bool) -> Self {
188 self.ignore_self = ignore;
189 self
190 }
191
192 /// Sets the maximum distance the ray can travel.
193 pub fn with_max_distance(mut self, max_distance: Scalar) -> Self {
194 self.max_distance = max_distance;
195 self
196 }
197
198 /// Sets the maximum number of allowed hits.
199 pub fn with_max_hits(mut self, max_hits: u32) -> Self {
200 self.max_hits = max_hits;
201 self
202 }
203
204 /// Sets the ray caster's [query filter](SpatialQueryFilter) that controls which colliders
205 /// should be included or excluded by raycasts.
206 pub fn with_query_filter(mut self, query_filter: SpatialQueryFilter) -> Self {
207 self.query_filter = query_filter;
208 self
209 }
210
211 /// Enables the [`RayCaster`].
212 pub fn enable(&mut self) {
213 self.enabled = true;
214 }
215
216 /// Disables the [`RayCaster`].
217 pub fn disable(&mut self) {
218 self.enabled = false;
219 }
220
221 /// Returns the global origin of the ray.
222 pub fn global_origin(&self) -> Vector {
223 self.global_origin
224 }
225
226 /// Returns the global direction of the ray.
227 pub fn global_direction(&self) -> Dir {
228 self.global_direction
229 }
230
231 /// Sets the global origin of the ray.
232 pub(crate) fn set_global_origin(&mut self, global_origin: Vector) {
233 self.global_origin = global_origin;
234 }
235
236 /// Sets the global direction of the ray.
237 pub(crate) fn set_global_direction(&mut self, global_direction: Dir) {
238 self.global_direction = global_direction;
239 }
240
241 #[cfg(all(
242 feature = "default-collider",
243 any(feature = "parry-f32", feature = "parry-f64")
244 ))]
245 pub(crate) fn cast(
246 &mut self,
247 caster_entity: Entity,
248 hits: &mut RayHits,
249 spatial_query: &SpatialQuery,
250 ) {
251 if self.ignore_self {
252 self.query_filter.excluded_entities.insert(caster_entity);
253 } else {
254 self.query_filter.excluded_entities.remove(&caster_entity);
255 }
256
257 hits.clear();
258
259 if self.max_hits == 1 {
260 let first_hit = spatial_query.cast_ray(
261 self.global_origin(),
262 self.global_direction(),
263 self.max_distance,
264 self.solid,
265 &self.query_filter,
266 );
267
268 if let Some(hit) = first_hit {
269 hits.push(hit);
270 }
271 } else {
272 hits.extend(spatial_query.ray_hits(
273 self.global_origin(),
274 self.global_direction(),
275 self.max_distance,
276 self.max_hits,
277 self.solid,
278 &self.query_filter,
279 ));
280 }
281 }
282
283 /// Returns the point at a given distance along the ray.
284 #[must_use]
285 pub fn get_point(&self, distance: Scalar) -> Vector {
286 self.origin + self.direction.adjust_precision() * distance
287 }
288
289 /// Like [`Self::get_point`], but returns the point in global coordinates.
290 #[must_use]
291 pub fn get_global_point(&self, distance: Scalar) -> Vector {
292 self.global_origin + self.global_direction.adjust_precision() * distance
293 }
294}
295
296fn on_add_ray_caster(mut world: DeferredWorld, ctx: HookContext) {
297 let ray_caster = world.get::<RayCaster>(ctx.entity).unwrap();
298 let max_hits = if ray_caster.max_hits == u32::MAX {
299 10
300 } else {
301 ray_caster.max_hits as usize
302 };
303
304 // Initialize capacity for hits
305 world.get_mut::<RayHits>(ctx.entity).unwrap().0 = Vec::with_capacity(max_hits);
306}
307
308/// Contains the hits of a ray cast by a [`RayCaster`].
309///
310/// The maximum number of hits depends on the value of `max_hits` in [`RayCaster`].
311///
312/// # Order
313///
314/// By default, the order of the hits is not guaranteed.
315///
316/// You can iterate the hits in the order of distance with `iter_sorted`.
317/// Note that this will create and sort a new vector instead of iterating over the existing one.
318///
319/// **Note**: When there are more hits than `max_hits`, **some hits will be missed**.
320/// If you want to guarantee that the closest hit is included, set `max_hits` to one.
321///
322/// # Example
323///
324/// ```
325#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
326#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
327/// use bevy::prelude::*;
328///
329/// fn print_hits(query: Query<&RayHits, With<RayCaster>>) {
330/// for hits in &query {
331/// // For the faster iterator that isn't sorted, use `.iter()`.
332/// for hit in hits.iter_sorted() {
333/// println!("Hit entity {} with distance {}", hit.entity, hit.distance);
334/// }
335/// }
336/// }
337/// ```
338#[derive(Component, Clone, Debug, Default, Deref, DerefMut, PartialEq, Reflect)]
339#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
340#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
341#[reflect(Component, Debug, Default, PartialEq)]
342pub struct RayHits(pub Vec<RayHitData>);
343
344impl RayHits {
345 /// Returns an iterator over the hits, sorted in ascending order according to the distance.
346 ///
347 /// Note that this allocates a new vector. If you don't need the hits in order, use `iter`.
348 pub fn iter_sorted(&self) -> alloc::vec::IntoIter<RayHitData> {
349 let mut vector = self.as_slice().to_vec();
350 vector.sort_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap());
351 vector.into_iter()
352 }
353}
354
355impl IntoIterator for RayHits {
356 type Item = RayHitData;
357 type IntoIter = alloc::vec::IntoIter<RayHitData>;
358
359 fn into_iter(self) -> Self::IntoIter {
360 self.0.into_iter()
361 }
362}
363
364impl<'a> IntoIterator for &'a RayHits {
365 type Item = &'a RayHitData;
366 type IntoIter = core::slice::Iter<'a, RayHitData>;
367
368 fn into_iter(self) -> Self::IntoIter {
369 self.0.iter()
370 }
371}
372
373impl<'a> IntoIterator for &'a mut RayHits {
374 type Item = &'a mut RayHitData;
375 type IntoIter = core::slice::IterMut<'a, RayHitData>;
376
377 fn into_iter(self) -> Self::IntoIter {
378 self.0.iter_mut()
379 }
380}
381
382impl MapEntities for RayHits {
383 fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
384 for hit in self {
385 hit.map_entities(entity_mapper);
386 }
387 }
388}
389
390/// Data related to a hit during a [raycast](spatial_query#raycasting).
391#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
392#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
393#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
394#[reflect(Debug, PartialEq)]
395pub struct RayHitData {
396 /// The entity of the collider that was hit by the ray.
397 pub entity: Entity,
398
399 /// How far the ray travelled. This is the distance between the ray origin and the point of intersection.
400 pub distance: Scalar,
401
402 /// The normal at the point of intersection, expressed in world space.
403 pub normal: Vector,
404}
405
406impl MapEntities for RayHitData {
407 fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
408 self.entity = entity_mapper.get_mapped(self.entity);
409 }
410}