bevy_math/direction.rs
1use crate::{
2 primitives::{Primitive2d, Primitive3d},
3 Quat, Rot2, Vec2, Vec3, Vec3A,
4};
5
6use core::f32::consts::FRAC_1_SQRT_2;
7use derive_more::derive::Into;
8
9#[cfg(feature = "bevy_reflect")]
10use bevy_reflect::Reflect;
11#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
12use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
13
14/// An error indicating that a direction is invalid.
15#[derive(Debug, PartialEq)]
16pub enum InvalidDirectionError {
17 /// The length of the direction vector is zero or very close to zero.
18 Zero,
19 /// The length of the direction vector is `std::f32::INFINITY`.
20 Infinite,
21 /// The length of the direction vector is `NaN`.
22 NaN,
23}
24
25impl InvalidDirectionError {
26 /// Creates an [`InvalidDirectionError`] from the length of an invalid direction vector.
27 pub fn from_length(length: f32) -> Self {
28 if length.is_nan() {
29 InvalidDirectionError::NaN
30 } else if !length.is_finite() {
31 // If the direction is non-finite but also not NaN, it must be infinite
32 InvalidDirectionError::Infinite
33 } else {
34 // If the direction is invalid but neither NaN nor infinite, it must be zero
35 InvalidDirectionError::Zero
36 }
37 }
38}
39
40impl core::fmt::Display for InvalidDirectionError {
41 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42 write!(
43 f,
44 "Direction can not be zero (or very close to zero), or non-finite."
45 )
46 }
47}
48
49/// Checks that a vector with the given squared length is normalized.
50///
51/// Warns for small error with a length threshold of approximately `1e-4`,
52/// and panics for large error with a length threshold of approximately `1e-2`.
53///
54/// The format used for the logged warning is `"Warning: {warning} The length is {length}`,
55/// and similarly for the error.
56#[cfg(debug_assertions)]
57fn assert_is_normalized(message: &str, length_squared: f32) {
58 let length_error_squared = (length_squared - 1.0).abs();
59
60 // Panic for large error and warn for slight error.
61 if length_error_squared > 2e-2 || length_error_squared.is_nan() {
62 // Length error is approximately 1e-2 or more.
63 panic!("Error: {message} The length is {}.", length_squared.sqrt());
64 } else if length_error_squared > 2e-4 {
65 // Length error is approximately 1e-4 or more.
66 eprintln!(
67 "Warning: {message} The length is {}.",
68 length_squared.sqrt()
69 );
70 }
71}
72
73/// A normalized vector pointing in a direction in 2D space
74#[deprecated(
75 since = "0.14.0",
76 note = "`Direction2d` has been renamed. Please use `Dir2` instead."
77)]
78pub type Direction2d = Dir2;
79
80/// A normalized vector pointing in a direction in 3D space
81#[deprecated(
82 since = "0.14.0",
83 note = "`Direction3d` has been renamed. Please use `Dir3` instead."
84)]
85pub type Direction3d = Dir3;
86
87/// A normalized vector pointing in a direction in 2D space
88#[derive(Clone, Copy, Debug, PartialEq)]
89#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
90#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
91#[cfg_attr(
92 all(feature = "serialize", feature = "bevy_reflect"),
93 reflect(Serialize, Deserialize)
94)]
95#[doc(alias = "Direction2d")]
96pub struct Dir2(Vec2);
97impl Primitive2d for Dir2 {}
98
99impl Dir2 {
100 /// A unit vector pointing along the positive X axis.
101 pub const X: Self = Self(Vec2::X);
102 /// A unit vector pointing along the positive Y axis.
103 pub const Y: Self = Self(Vec2::Y);
104 /// A unit vector pointing along the negative X axis.
105 pub const NEG_X: Self = Self(Vec2::NEG_X);
106 /// A unit vector pointing along the negative Y axis.
107 pub const NEG_Y: Self = Self(Vec2::NEG_Y);
108 /// The directional axes.
109 pub const AXES: [Self; 2] = [Self::X, Self::Y];
110
111 /// The "north" direction, equivalent to [`Dir2::Y`].
112 pub const NORTH: Self = Self(Vec2::Y);
113 /// The "south" direction, equivalent to [`Dir2::NEG_Y`].
114 pub const SOUTH: Self = Self(Vec2::NEG_Y);
115 /// The "east" direction, equivalent to [`Dir2::X`].
116 pub const EAST: Self = Self(Vec2::X);
117 /// The "west" direction, equivalent to [`Dir2::NEG_X`].
118 pub const WEST: Self = Self(Vec2::NEG_X);
119 /// The "north-east" direction, between [`Dir2::NORTH`] and [`Dir2::EAST`].
120 pub const NORTH_EAST: Self = Self(Vec2::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2));
121 /// The "north-west" direction, between [`Dir2::NORTH`] and [`Dir2::WEST`].
122 pub const NORTH_WEST: Self = Self(Vec2::new(-FRAC_1_SQRT_2, FRAC_1_SQRT_2));
123 /// The "south-east" direction, between [`Dir2::SOUTH`] and [`Dir2::EAST`].
124 pub const SOUTH_EAST: Self = Self(Vec2::new(FRAC_1_SQRT_2, -FRAC_1_SQRT_2));
125 /// The "south-west" direction, between [`Dir2::SOUTH`] and [`Dir2::WEST`].
126 pub const SOUTH_WEST: Self = Self(Vec2::new(-FRAC_1_SQRT_2, -FRAC_1_SQRT_2));
127
128 /// Create a direction from a finite, nonzero [`Vec2`], normalizing it.
129 ///
130 /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length
131 /// of the given vector is zero (or very close to zero), infinite, or `NaN`.
132 pub fn new(value: Vec2) -> Result<Self, InvalidDirectionError> {
133 Self::new_and_length(value).map(|(dir, _)| dir)
134 }
135
136 /// Create a [`Dir2`] from a [`Vec2`] that is already normalized.
137 ///
138 /// # Warning
139 ///
140 /// `value` must be normalized, i.e its length must be `1.0`.
141 pub fn new_unchecked(value: Vec2) -> Self {
142 #[cfg(debug_assertions)]
143 assert_is_normalized(
144 "The vector given to `Dir2::new_unchecked` is not normalized.",
145 value.length_squared(),
146 );
147
148 Self(value)
149 }
150
151 /// Create a direction from a finite, nonzero [`Vec2`], normalizing it and
152 /// also returning its original length.
153 ///
154 /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length
155 /// of the given vector is zero (or very close to zero), infinite, or `NaN`.
156 pub fn new_and_length(value: Vec2) -> Result<(Self, f32), InvalidDirectionError> {
157 let length = value.length();
158 let direction = (length.is_finite() && length > 0.0).then_some(value / length);
159
160 direction
161 .map(|dir| (Self(dir), length))
162 .ok_or(InvalidDirectionError::from_length(length))
163 }
164
165 /// Create a direction from its `x` and `y` components.
166 ///
167 /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length
168 /// of the vector formed by the components is zero (or very close to zero), infinite, or `NaN`.
169 pub fn from_xy(x: f32, y: f32) -> Result<Self, InvalidDirectionError> {
170 Self::new(Vec2::new(x, y))
171 }
172
173 /// Create a direction from its `x` and `y` components, assuming the resulting vector is normalized.
174 ///
175 /// # Warning
176 ///
177 /// The vector produced from `x` and `y` must be normalized, i.e its length must be `1.0`.
178 pub fn from_xy_unchecked(x: f32, y: f32) -> Self {
179 Self::new_unchecked(Vec2::new(x, y))
180 }
181
182 /// Returns the inner [`Vec2`]
183 pub const fn as_vec2(&self) -> Vec2 {
184 self.0
185 }
186
187 /// Performs a spherical linear interpolation between `self` and `rhs`
188 /// based on the value `s`.
189 ///
190 /// This corresponds to interpolating between the two directions at a constant angular velocity.
191 ///
192 /// When `s == 0.0`, the result will be equal to `self`.
193 /// When `s == 1.0`, the result will be equal to `rhs`.
194 ///
195 /// # Example
196 ///
197 /// ```
198 /// # use bevy_math::Dir2;
199 /// # use approx::{assert_relative_eq, RelativeEq};
200 /// #
201 /// let dir1 = Dir2::X;
202 /// let dir2 = Dir2::Y;
203 ///
204 /// let result1 = dir1.slerp(dir2, 1.0 / 3.0);
205 /// assert_relative_eq!(result1, Dir2::from_xy(0.75_f32.sqrt(), 0.5).unwrap());
206 ///
207 /// let result2 = dir1.slerp(dir2, 0.5);
208 /// assert_relative_eq!(result2, Dir2::from_xy(0.5_f32.sqrt(), 0.5_f32.sqrt()).unwrap());
209 /// ```
210 #[inline]
211 pub fn slerp(self, rhs: Self, s: f32) -> Self {
212 let angle = self.angle_to(rhs.0);
213 Rot2::radians(angle * s) * self
214 }
215
216 /// Get the rotation that rotates this direction to `other`.
217 #[inline]
218 pub fn rotation_to(self, other: Self) -> Rot2 {
219 // Rotate `self` to X-axis, then X-axis to `other`:
220 other.rotation_from_x() * self.rotation_to_x()
221 }
222
223 /// Get the rotation that rotates `other` to this direction.
224 #[inline]
225 pub fn rotation_from(self, other: Self) -> Rot2 {
226 other.rotation_to(self)
227 }
228
229 /// Get the rotation that rotates the X-axis to this direction.
230 #[inline]
231 pub fn rotation_from_x(self) -> Rot2 {
232 Rot2::from_sin_cos(self.0.y, self.0.x)
233 }
234
235 /// Get the rotation that rotates this direction to the X-axis.
236 #[inline]
237 pub fn rotation_to_x(self) -> Rot2 {
238 // (This is cheap, it just negates one component.)
239 self.rotation_from_x().inverse()
240 }
241
242 /// Get the rotation that rotates the Y-axis to this direction.
243 #[inline]
244 pub fn rotation_from_y(self) -> Rot2 {
245 // `x <- y`, `y <- -x` correspond to rotating clockwise by pi/2;
246 // this transforms the Y-axis into the X-axis, maintaining the relative position
247 // of our direction. Then we just use the same technique as `rotation_from_x`.
248 Rot2::from_sin_cos(-self.0.x, self.0.y)
249 }
250
251 /// Get the rotation that rotates this direction to the Y-axis.
252 #[inline]
253 pub fn rotation_to_y(self) -> Rot2 {
254 self.rotation_from_y().inverse()
255 }
256
257 /// Returns `self` after an approximate normalization, assuming the value is already nearly normalized.
258 /// Useful for preventing numerical error accumulation.
259 /// See [`Dir3::fast_renormalize`] for an example of when such error accumulation might occur.
260 #[inline]
261 pub fn fast_renormalize(self) -> Self {
262 let length_squared = self.0.length_squared();
263 // Based on a Taylor approximation of the inverse square root, see [`Dir3::fast_renormalize`] for more details.
264 Self(self * (0.5 * (3.0 - length_squared)))
265 }
266}
267
268impl TryFrom<Vec2> for Dir2 {
269 type Error = InvalidDirectionError;
270
271 fn try_from(value: Vec2) -> Result<Self, Self::Error> {
272 Self::new(value)
273 }
274}
275
276impl From<Dir2> for Vec2 {
277 fn from(value: Dir2) -> Self {
278 value.as_vec2()
279 }
280}
281
282impl core::ops::Deref for Dir2 {
283 type Target = Vec2;
284 fn deref(&self) -> &Self::Target {
285 &self.0
286 }
287}
288
289impl core::ops::Neg for Dir2 {
290 type Output = Self;
291 fn neg(self) -> Self::Output {
292 Self(-self.0)
293 }
294}
295
296impl core::ops::Mul<f32> for Dir2 {
297 type Output = Vec2;
298 fn mul(self, rhs: f32) -> Self::Output {
299 self.0 * rhs
300 }
301}
302
303impl core::ops::Mul<Dir2> for f32 {
304 type Output = Vec2;
305 fn mul(self, rhs: Dir2) -> Self::Output {
306 self * rhs.0
307 }
308}
309
310impl core::ops::Mul<Dir2> for Rot2 {
311 type Output = Dir2;
312
313 /// Rotates the [`Dir2`] using a [`Rot2`].
314 fn mul(self, direction: Dir2) -> Self::Output {
315 let rotated = self * *direction;
316
317 #[cfg(debug_assertions)]
318 assert_is_normalized(
319 "`Dir2` is denormalized after rotation.",
320 rotated.length_squared(),
321 );
322
323 Dir2(rotated)
324 }
325}
326
327#[cfg(any(feature = "approx", test))]
328impl approx::AbsDiffEq for Dir2 {
329 type Epsilon = f32;
330 fn default_epsilon() -> f32 {
331 f32::EPSILON
332 }
333 fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {
334 self.as_ref().abs_diff_eq(other.as_ref(), epsilon)
335 }
336}
337
338#[cfg(any(feature = "approx", test))]
339impl approx::RelativeEq for Dir2 {
340 fn default_max_relative() -> f32 {
341 f32::EPSILON
342 }
343 fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {
344 self.as_ref()
345 .relative_eq(other.as_ref(), epsilon, max_relative)
346 }
347}
348
349#[cfg(any(feature = "approx", test))]
350impl approx::UlpsEq for Dir2 {
351 fn default_max_ulps() -> u32 {
352 4
353 }
354 fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool {
355 self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps)
356 }
357}
358
359/// A normalized vector pointing in a direction in 3D space
360#[derive(Clone, Copy, Debug, PartialEq, Into)]
361#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
362#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
363#[cfg_attr(
364 all(feature = "serialize", feature = "bevy_reflect"),
365 reflect(Serialize, Deserialize)
366)]
367#[doc(alias = "Direction3d")]
368pub struct Dir3(Vec3);
369impl Primitive3d for Dir3 {}
370
371impl Dir3 {
372 /// A unit vector pointing along the positive X axis.
373 pub const X: Self = Self(Vec3::X);
374 /// A unit vector pointing along the positive Y axis.
375 pub const Y: Self = Self(Vec3::Y);
376 /// A unit vector pointing along the positive Z axis.
377 pub const Z: Self = Self(Vec3::Z);
378 /// A unit vector pointing along the negative X axis.
379 pub const NEG_X: Self = Self(Vec3::NEG_X);
380 /// A unit vector pointing along the negative Y axis.
381 pub const NEG_Y: Self = Self(Vec3::NEG_Y);
382 /// A unit vector pointing along the negative Z axis.
383 pub const NEG_Z: Self = Self(Vec3::NEG_Z);
384 /// The directional axes.
385 pub const AXES: [Self; 3] = [Self::X, Self::Y, Self::Z];
386
387 /// Create a direction from a finite, nonzero [`Vec3`], normalizing it.
388 ///
389 /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length
390 /// of the given vector is zero (or very close to zero), infinite, or `NaN`.
391 pub fn new(value: Vec3) -> Result<Self, InvalidDirectionError> {
392 Self::new_and_length(value).map(|(dir, _)| dir)
393 }
394
395 /// Create a [`Dir3`] from a [`Vec3`] that is already normalized.
396 ///
397 /// # Warning
398 ///
399 /// `value` must be normalized, i.e its length must be `1.0`.
400 pub fn new_unchecked(value: Vec3) -> Self {
401 #[cfg(debug_assertions)]
402 assert_is_normalized(
403 "The vector given to `Dir3::new_unchecked` is not normalized.",
404 value.length_squared(),
405 );
406
407 Self(value)
408 }
409
410 /// Create a direction from a finite, nonzero [`Vec3`], normalizing it and
411 /// also returning its original length.
412 ///
413 /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length
414 /// of the given vector is zero (or very close to zero), infinite, or `NaN`.
415 pub fn new_and_length(value: Vec3) -> Result<(Self, f32), InvalidDirectionError> {
416 let length = value.length();
417 let direction = (length.is_finite() && length > 0.0).then_some(value / length);
418
419 direction
420 .map(|dir| (Self(dir), length))
421 .ok_or(InvalidDirectionError::from_length(length))
422 }
423
424 /// Create a direction from its `x`, `y`, and `z` components.
425 ///
426 /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length
427 /// of the vector formed by the components is zero (or very close to zero), infinite, or `NaN`.
428 pub fn from_xyz(x: f32, y: f32, z: f32) -> Result<Self, InvalidDirectionError> {
429 Self::new(Vec3::new(x, y, z))
430 }
431
432 /// Create a direction from its `x`, `y`, and `z` components, assuming the resulting vector is normalized.
433 ///
434 /// # Warning
435 ///
436 /// The vector produced from `x`, `y`, and `z` must be normalized, i.e its length must be `1.0`.
437 pub fn from_xyz_unchecked(x: f32, y: f32, z: f32) -> Self {
438 Self::new_unchecked(Vec3::new(x, y, z))
439 }
440
441 /// Returns the inner [`Vec3`]
442 pub const fn as_vec3(&self) -> Vec3 {
443 self.0
444 }
445
446 /// Performs a spherical linear interpolation between `self` and `rhs`
447 /// based on the value `s`.
448 ///
449 /// This corresponds to interpolating between the two directions at a constant angular velocity.
450 ///
451 /// When `s == 0.0`, the result will be equal to `self`.
452 /// When `s == 1.0`, the result will be equal to `rhs`.
453 ///
454 /// # Example
455 ///
456 /// ```
457 /// # use bevy_math::Dir3;
458 /// # use approx::{assert_relative_eq, RelativeEq};
459 /// #
460 /// let dir1 = Dir3::X;
461 /// let dir2 = Dir3::Y;
462 ///
463 /// let result1 = dir1.slerp(dir2, 1.0 / 3.0);
464 /// assert_relative_eq!(
465 /// result1,
466 /// Dir3::from_xyz(0.75_f32.sqrt(), 0.5, 0.0).unwrap(),
467 /// epsilon = 0.000001
468 /// );
469 ///
470 /// let result2 = dir1.slerp(dir2, 0.5);
471 /// assert_relative_eq!(result2, Dir3::from_xyz(0.5_f32.sqrt(), 0.5_f32.sqrt(), 0.0).unwrap());
472 /// ```
473 #[inline]
474 pub fn slerp(self, rhs: Self, s: f32) -> Self {
475 let quat = Quat::IDENTITY.slerp(Quat::from_rotation_arc(self.0, rhs.0), s);
476 Dir3(quat.mul_vec3(self.0))
477 }
478
479 /// Returns `self` after an approximate normalization, assuming the value is already nearly normalized.
480 /// Useful for preventing numerical error accumulation.
481 ///
482 /// # Example
483 /// The following seemingly benign code would start accumulating errors over time,
484 /// leading to `dir` eventually not being normalized anymore.
485 /// ```
486 /// # use bevy_math::prelude::*;
487 /// # let N: usize = 200;
488 /// let mut dir = Dir3::X;
489 /// let quaternion = Quat::from_euler(EulerRot::XYZ, 1.0, 2.0, 3.0);
490 /// for i in 0..N {
491 /// dir = quaternion * dir;
492 /// }
493 /// ```
494 /// Instead, do the following.
495 /// ```
496 /// # use bevy_math::prelude::*;
497 /// # let N: usize = 200;
498 /// let mut dir = Dir3::X;
499 /// let quaternion = Quat::from_euler(EulerRot::XYZ, 1.0, 2.0, 3.0);
500 /// for i in 0..N {
501 /// dir = quaternion * dir;
502 /// dir = dir.fast_renormalize();
503 /// }
504 /// ```
505 #[inline]
506 pub fn fast_renormalize(self) -> Self {
507 // We numerically approximate the inverse square root by a Taylor series around 1
508 // As we expect the error (x := length_squared - 1) to be small
509 // inverse_sqrt(length_squared) = (1 + x)^(-1/2) = 1 - 1/2 x + O(x²)
510 // inverse_sqrt(length_squared) ≈ 1 - 1/2 (length_squared - 1) = 1/2 (3 - length_squared)
511
512 // Iterative calls to this method quickly converge to a normalized value,
513 // so long as the denormalization is not large ~ O(1/10).
514 // One iteration can be described as:
515 // l_sq <- l_sq * (1 - 1/2 (l_sq - 1))²;
516 // Rewriting in terms of the error x:
517 // 1 + x <- (1 + x) * (1 - 1/2 x)²
518 // 1 + x <- (1 + x) * (1 - x + 1/4 x²)
519 // 1 + x <- 1 - x + 1/4 x² + x - x² + 1/4 x³
520 // x <- -1/4 x² (3 - x)
521 // If the error is small, say in a range of (-1/2, 1/2), then:
522 // |-1/4 x² (3 - x)| <= (3/4 + 1/4 * |x|) * x² <= (3/4 + 1/4 * 1/2) * x² < x² < 1/2 x
523 // Therefore the sequence of iterates converges to 0 error as a second order method.
524
525 let length_squared = self.0.length_squared();
526 Self(self * (0.5 * (3.0 - length_squared)))
527 }
528}
529
530impl TryFrom<Vec3> for Dir3 {
531 type Error = InvalidDirectionError;
532
533 fn try_from(value: Vec3) -> Result<Self, Self::Error> {
534 Self::new(value)
535 }
536}
537
538impl core::ops::Deref for Dir3 {
539 type Target = Vec3;
540 fn deref(&self) -> &Self::Target {
541 &self.0
542 }
543}
544
545impl core::ops::Neg for Dir3 {
546 type Output = Self;
547 fn neg(self) -> Self::Output {
548 Self(-self.0)
549 }
550}
551
552impl core::ops::Mul<f32> for Dir3 {
553 type Output = Vec3;
554 fn mul(self, rhs: f32) -> Self::Output {
555 self.0 * rhs
556 }
557}
558
559impl core::ops::Mul<Dir3> for f32 {
560 type Output = Vec3;
561 fn mul(self, rhs: Dir3) -> Self::Output {
562 self * rhs.0
563 }
564}
565
566impl core::ops::Mul<Dir3> for Quat {
567 type Output = Dir3;
568
569 /// Rotates the [`Dir3`] using a [`Quat`].
570 fn mul(self, direction: Dir3) -> Self::Output {
571 let rotated = self * *direction;
572
573 #[cfg(debug_assertions)]
574 assert_is_normalized(
575 "`Dir3` is denormalized after rotation.",
576 rotated.length_squared(),
577 );
578
579 Dir3(rotated)
580 }
581}
582
583#[cfg(feature = "approx")]
584impl approx::AbsDiffEq for Dir3 {
585 type Epsilon = f32;
586 fn default_epsilon() -> f32 {
587 f32::EPSILON
588 }
589 fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {
590 self.as_ref().abs_diff_eq(other.as_ref(), epsilon)
591 }
592}
593
594#[cfg(feature = "approx")]
595impl approx::RelativeEq for Dir3 {
596 fn default_max_relative() -> f32 {
597 f32::EPSILON
598 }
599 fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {
600 self.as_ref()
601 .relative_eq(other.as_ref(), epsilon, max_relative)
602 }
603}
604
605#[cfg(feature = "approx")]
606impl approx::UlpsEq for Dir3 {
607 fn default_max_ulps() -> u32 {
608 4
609 }
610 fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool {
611 self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps)
612 }
613}
614
615/// A normalized SIMD vector pointing in a direction in 3D space.
616///
617/// This type stores a 16 byte aligned [`Vec3A`].
618/// This may or may not be faster than [`Dir3`]: make sure to benchmark!
619#[derive(Clone, Copy, Debug, PartialEq)]
620#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
621#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
622#[cfg_attr(
623 all(feature = "serialize", feature = "bevy_reflect"),
624 reflect(Serialize, Deserialize)
625)]
626#[doc(alias = "Direction3dA")]
627pub struct Dir3A(Vec3A);
628impl Primitive3d for Dir3A {}
629
630impl Dir3A {
631 /// A unit vector pointing along the positive X axis.
632 pub const X: Self = Self(Vec3A::X);
633 /// A unit vector pointing along the positive Y axis.
634 pub const Y: Self = Self(Vec3A::Y);
635 /// A unit vector pointing along the positive Z axis.
636 pub const Z: Self = Self(Vec3A::Z);
637 /// A unit vector pointing along the negative X axis.
638 pub const NEG_X: Self = Self(Vec3A::NEG_X);
639 /// A unit vector pointing along the negative Y axis.
640 pub const NEG_Y: Self = Self(Vec3A::NEG_Y);
641 /// A unit vector pointing along the negative Z axis.
642 pub const NEG_Z: Self = Self(Vec3A::NEG_Z);
643 /// The directional axes.
644 pub const AXES: [Self; 3] = [Self::X, Self::Y, Self::Z];
645
646 /// Create a direction from a finite, nonzero [`Vec3A`], normalizing it.
647 ///
648 /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length
649 /// of the given vector is zero (or very close to zero), infinite, or `NaN`.
650 pub fn new(value: Vec3A) -> Result<Self, InvalidDirectionError> {
651 Self::new_and_length(value).map(|(dir, _)| dir)
652 }
653
654 /// Create a [`Dir3A`] from a [`Vec3A`] that is already normalized.
655 ///
656 /// # Warning
657 ///
658 /// `value` must be normalized, i.e its length must be `1.0`.
659 pub fn new_unchecked(value: Vec3A) -> Self {
660 #[cfg(debug_assertions)]
661 assert_is_normalized(
662 "The vector given to `Dir3A::new_unchecked` is not normalized.",
663 value.length_squared(),
664 );
665
666 Self(value)
667 }
668
669 /// Create a direction from a finite, nonzero [`Vec3A`], normalizing it and
670 /// also returning its original length.
671 ///
672 /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length
673 /// of the given vector is zero (or very close to zero), infinite, or `NaN`.
674 pub fn new_and_length(value: Vec3A) -> Result<(Self, f32), InvalidDirectionError> {
675 let length = value.length();
676 let direction = (length.is_finite() && length > 0.0).then_some(value / length);
677
678 direction
679 .map(|dir| (Self(dir), length))
680 .ok_or(InvalidDirectionError::from_length(length))
681 }
682
683 /// Create a direction from its `x`, `y`, and `z` components.
684 ///
685 /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length
686 /// of the vector formed by the components is zero (or very close to zero), infinite, or `NaN`.
687 pub fn from_xyz(x: f32, y: f32, z: f32) -> Result<Self, InvalidDirectionError> {
688 Self::new(Vec3A::new(x, y, z))
689 }
690
691 /// Create a direction from its `x`, `y`, and `z` components, assuming the resulting vector is normalized.
692 ///
693 /// # Warning
694 ///
695 /// The vector produced from `x`, `y`, and `z` must be normalized, i.e its length must be `1.0`.
696 pub fn from_xyz_unchecked(x: f32, y: f32, z: f32) -> Self {
697 Self::new_unchecked(Vec3A::new(x, y, z))
698 }
699
700 /// Returns the inner [`Vec3A`]
701 pub const fn as_vec3a(&self) -> Vec3A {
702 self.0
703 }
704
705 /// Performs a spherical linear interpolation between `self` and `rhs`
706 /// based on the value `s`.
707 ///
708 /// This corresponds to interpolating between the two directions at a constant angular velocity.
709 ///
710 /// When `s == 0.0`, the result will be equal to `self`.
711 /// When `s == 1.0`, the result will be equal to `rhs`.
712 ///
713 /// # Example
714 ///
715 /// ```
716 /// # use bevy_math::Dir3A;
717 /// # use approx::{assert_relative_eq, RelativeEq};
718 /// #
719 /// let dir1 = Dir3A::X;
720 /// let dir2 = Dir3A::Y;
721 ///
722 /// let result1 = dir1.slerp(dir2, 1.0 / 3.0);
723 /// assert_relative_eq!(
724 /// result1,
725 /// Dir3A::from_xyz(0.75_f32.sqrt(), 0.5, 0.0).unwrap(),
726 /// epsilon = 0.000001
727 /// );
728 ///
729 /// let result2 = dir1.slerp(dir2, 0.5);
730 /// assert_relative_eq!(result2, Dir3A::from_xyz(0.5_f32.sqrt(), 0.5_f32.sqrt(), 0.0).unwrap());
731 /// ```
732 #[inline]
733 pub fn slerp(self, rhs: Self, s: f32) -> Self {
734 let quat = Quat::IDENTITY.slerp(
735 Quat::from_rotation_arc(Vec3::from(self.0), Vec3::from(rhs.0)),
736 s,
737 );
738 Dir3A(quat.mul_vec3a(self.0))
739 }
740
741 /// Returns `self` after an approximate normalization, assuming the value is already nearly normalized.
742 /// Useful for preventing numerical error accumulation.
743 ///
744 /// See [`Dir3::fast_renormalize`] for an example of when such error accumulation might occur.
745 #[inline]
746 pub fn fast_renormalize(self) -> Self {
747 let length_squared = self.0.length_squared();
748 // Based on a Taylor approximation of the inverse square root, see [`Dir3::fast_renormalize`] for more details.
749 Self(self * (0.5 * (3.0 - length_squared)))
750 }
751}
752
753impl From<Dir3> for Dir3A {
754 fn from(value: Dir3) -> Self {
755 Self(value.0.into())
756 }
757}
758
759impl From<Dir3A> for Dir3 {
760 fn from(value: Dir3A) -> Self {
761 Self(value.0.into())
762 }
763}
764
765impl TryFrom<Vec3A> for Dir3A {
766 type Error = InvalidDirectionError;
767
768 fn try_from(value: Vec3A) -> Result<Self, Self::Error> {
769 Self::new(value)
770 }
771}
772
773impl From<Dir3A> for Vec3A {
774 fn from(value: Dir3A) -> Self {
775 value.0
776 }
777}
778
779impl core::ops::Deref for Dir3A {
780 type Target = Vec3A;
781 fn deref(&self) -> &Self::Target {
782 &self.0
783 }
784}
785
786impl core::ops::Neg for Dir3A {
787 type Output = Self;
788 fn neg(self) -> Self::Output {
789 Self(-self.0)
790 }
791}
792
793impl core::ops::Mul<f32> for Dir3A {
794 type Output = Vec3A;
795 fn mul(self, rhs: f32) -> Self::Output {
796 self.0 * rhs
797 }
798}
799
800impl core::ops::Mul<Dir3A> for f32 {
801 type Output = Vec3A;
802 fn mul(self, rhs: Dir3A) -> Self::Output {
803 self * rhs.0
804 }
805}
806
807impl core::ops::Mul<Dir3A> for Quat {
808 type Output = Dir3A;
809
810 /// Rotates the [`Dir3A`] using a [`Quat`].
811 fn mul(self, direction: Dir3A) -> Self::Output {
812 let rotated = self * *direction;
813
814 #[cfg(debug_assertions)]
815 assert_is_normalized(
816 "`Dir3A` is denormalized after rotation.",
817 rotated.length_squared(),
818 );
819
820 Dir3A(rotated)
821 }
822}
823
824#[cfg(feature = "approx")]
825impl approx::AbsDiffEq for Dir3A {
826 type Epsilon = f32;
827 fn default_epsilon() -> f32 {
828 f32::EPSILON
829 }
830 fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {
831 self.as_ref().abs_diff_eq(other.as_ref(), epsilon)
832 }
833}
834
835#[cfg(feature = "approx")]
836impl approx::RelativeEq for Dir3A {
837 fn default_max_relative() -> f32 {
838 f32::EPSILON
839 }
840 fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {
841 self.as_ref()
842 .relative_eq(other.as_ref(), epsilon, max_relative)
843 }
844}
845
846#[cfg(feature = "approx")]
847impl approx::UlpsEq for Dir3A {
848 fn default_max_ulps() -> u32 {
849 4
850 }
851 fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool {
852 self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps)
853 }
854}
855
856#[cfg(test)]
857mod tests {
858 use crate::ops;
859
860 use super::*;
861 use approx::assert_relative_eq;
862
863 #[test]
864 fn dir2_creation() {
865 assert_eq!(Dir2::new(Vec2::X * 12.5), Ok(Dir2::X));
866 assert_eq!(
867 Dir2::new(Vec2::new(0.0, 0.0)),
868 Err(InvalidDirectionError::Zero)
869 );
870 assert_eq!(
871 Dir2::new(Vec2::new(f32::INFINITY, 0.0)),
872 Err(InvalidDirectionError::Infinite)
873 );
874 assert_eq!(
875 Dir2::new(Vec2::new(f32::NEG_INFINITY, 0.0)),
876 Err(InvalidDirectionError::Infinite)
877 );
878 assert_eq!(
879 Dir2::new(Vec2::new(f32::NAN, 0.0)),
880 Err(InvalidDirectionError::NaN)
881 );
882 assert_eq!(Dir2::new_and_length(Vec2::X * 6.5), Ok((Dir2::X, 6.5)));
883 }
884
885 #[test]
886 fn dir2_slerp() {
887 assert_relative_eq!(
888 Dir2::X.slerp(Dir2::Y, 0.5),
889 Dir2::from_xy(0.5_f32.sqrt(), 0.5_f32.sqrt()).unwrap()
890 );
891 assert_eq!(Dir2::Y.slerp(Dir2::X, 0.0), Dir2::Y);
892 assert_relative_eq!(Dir2::X.slerp(Dir2::Y, 1.0), Dir2::Y);
893 assert_relative_eq!(
894 Dir2::Y.slerp(Dir2::X, 1.0 / 3.0),
895 Dir2::from_xy(0.5, 0.75_f32.sqrt()).unwrap()
896 );
897 assert_relative_eq!(
898 Dir2::X.slerp(Dir2::Y, 2.0 / 3.0),
899 Dir2::from_xy(0.5, 0.75_f32.sqrt()).unwrap()
900 );
901 }
902
903 #[test]
904 fn dir2_to_rotation2d() {
905 // assert_relative_eq!(Dir2::EAST.rotation_to(Dir2::NORTH_EAST), Rot2::FRAC_PI_4);
906 // assert_relative_eq!(Dir2::NORTH.rotation_from(Dir2::NORTH_EAST), Rot2::FRAC_PI_4);
907 // assert_relative_eq!(Dir2::SOUTH.rotation_to_x(), Rot2::FRAC_PI_2);
908 // assert_relative_eq!(Dir2::SOUTH.rotation_to_y(), Rot2::PI);
909 // assert_relative_eq!(Dir2::NORTH_WEST.rotation_from_x(), Rot2::degrees(135.0));
910 // assert_relative_eq!(Dir2::NORTH_WEST.rotation_from_y(), Rot2::FRAC_PI_4);
911 }
912
913 #[test]
914 fn dir2_renorm() {
915 // Evil denormalized Rot2
916 let (sin, cos) = ops::sin_cos(1.0_f32);
917 let rot2 = Rot2::from_sin_cos(sin * (1.0 + 1e-5), cos * (1.0 + 1e-5));
918 let mut dir_a = Dir2::X;
919 let mut dir_b = Dir2::X;
920
921 // We test that renormalizing an already normalized dir doesn't do anything
922 assert_relative_eq!(dir_b, dir_b.fast_renormalize(), epsilon = 0.000001);
923
924 for _ in 0..50 {
925 dir_a = rot2 * dir_a;
926 dir_b = rot2 * dir_b;
927 dir_b = dir_b.fast_renormalize();
928 }
929
930 // `dir_a` should've gotten denormalized, meanwhile `dir_b` should stay normalized.
931 assert!(
932 !dir_a.is_normalized(),
933 "Dernormalization doesn't work, test is faulty"
934 );
935 assert!(dir_b.is_normalized(), "Renormalisation did not work.");
936 }
937
938 #[test]
939 fn dir3_creation() {
940 assert_eq!(Dir3::new(Vec3::X * 12.5), Ok(Dir3::X));
941 assert_eq!(
942 Dir3::new(Vec3::new(0.0, 0.0, 0.0)),
943 Err(InvalidDirectionError::Zero)
944 );
945 assert_eq!(
946 Dir3::new(Vec3::new(f32::INFINITY, 0.0, 0.0)),
947 Err(InvalidDirectionError::Infinite)
948 );
949 assert_eq!(
950 Dir3::new(Vec3::new(f32::NEG_INFINITY, 0.0, 0.0)),
951 Err(InvalidDirectionError::Infinite)
952 );
953 assert_eq!(
954 Dir3::new(Vec3::new(f32::NAN, 0.0, 0.0)),
955 Err(InvalidDirectionError::NaN)
956 );
957 assert_eq!(Dir3::new_and_length(Vec3::X * 6.5), Ok((Dir3::X, 6.5)));
958
959 // Test rotation
960 assert!(
961 (Quat::from_rotation_z(core::f32::consts::FRAC_PI_2) * Dir3::X)
962 .abs_diff_eq(Vec3::Y, 10e-6)
963 );
964 }
965
966 #[test]
967 fn dir3_slerp() {
968 // assert_relative_eq!(
969 // Dir3::X.slerp(Dir3::Y, 0.5),
970 // Dir3::from_xyz(0.5f32.sqrt(), 0.5f32.sqrt(), 0.0).unwrap()
971 // );
972 // assert_relative_eq!(Dir3::Y.slerp(Dir3::Z, 0.0), Dir3::Y);
973 // assert_relative_eq!(Dir3::Z.slerp(Dir3::X, 1.0), Dir3::X, epsilon = 0.000001);
974 // assert_relative_eq!(
975 // Dir3::X.slerp(Dir3::Z, 1.0 / 3.0),
976 // Dir3::from_xyz(0.75f32.sqrt(), 0.0, 0.5).unwrap(),
977 // epsilon = 0.000001
978 // );
979 // assert_relative_eq!(
980 // Dir3::Z.slerp(Dir3::Y, 2.0 / 3.0),
981 // Dir3::from_xyz(0.0, 0.75f32.sqrt(), 0.5).unwrap()
982 // );
983 }
984
985 #[test]
986 fn dir3_renorm() {
987 // Evil denormalized quaternion
988 let rot3 = Quat::from_euler(glam::EulerRot::XYZ, 1.0, 2.0, 3.0) * (1.0 + 1e-5);
989 let mut dir_a = Dir3::X;
990 let mut dir_b = Dir3::X;
991
992 // We test that renormalizing an already normalized dir doesn't do anything
993 // assert_relative_eq!(dir_b, dir_b.fast_renormalize(), epsilon = 0.000001);
994
995 for _ in 0..50 {
996 dir_a = rot3 * dir_a;
997 dir_b = rot3 * dir_b;
998 dir_b = dir_b.fast_renormalize();
999 }
1000
1001 // `dir_a` should've gotten denormalized, meanwhile `dir_b` should stay normalized.
1002 assert!(
1003 !dir_a.is_normalized(),
1004 "Dernormalization doesn't work, test is faulty"
1005 );
1006 assert!(dir_b.is_normalized(), "Renormalisation did not work.");
1007 }
1008
1009 #[test]
1010 fn dir3a_creation() {
1011 assert_eq!(Dir3A::new(Vec3A::X * 12.5), Ok(Dir3A::X));
1012 assert_eq!(
1013 Dir3A::new(Vec3A::new(0.0, 0.0, 0.0)),
1014 Err(InvalidDirectionError::Zero)
1015 );
1016 assert_eq!(
1017 Dir3A::new(Vec3A::new(f32::INFINITY, 0.0, 0.0)),
1018 Err(InvalidDirectionError::Infinite)
1019 );
1020 assert_eq!(
1021 Dir3A::new(Vec3A::new(f32::NEG_INFINITY, 0.0, 0.0)),
1022 Err(InvalidDirectionError::Infinite)
1023 );
1024 assert_eq!(
1025 Dir3A::new(Vec3A::new(f32::NAN, 0.0, 0.0)),
1026 Err(InvalidDirectionError::NaN)
1027 );
1028 assert_eq!(Dir3A::new_and_length(Vec3A::X * 6.5), Ok((Dir3A::X, 6.5)));
1029
1030 // Test rotation
1031 assert!(
1032 (Quat::from_rotation_z(core::f32::consts::FRAC_PI_2) * Dir3A::X)
1033 .abs_diff_eq(Vec3A::Y, 10e-6)
1034 );
1035 }
1036
1037 #[test]
1038 fn dir3a_slerp() {
1039 // assert_relative_eq!(
1040 // Dir3A::X.slerp(Dir3A::Y, 0.5),
1041 // Dir3A::from_xyz(0.5f32.sqrt(), 0.5f32.sqrt(), 0.0).unwrap()
1042 // );
1043 // assert_relative_eq!(Dir3A::Y.slerp(Dir3A::Z, 0.0), Dir3A::Y);
1044 // assert_relative_eq!(Dir3A::Z.slerp(Dir3A::X, 1.0), Dir3A::X, epsilon = 0.000001);
1045 // assert_relative_eq!(
1046 // Dir3A::X.slerp(Dir3A::Z, 1.0 / 3.0),
1047 // Dir3A::from_xyz(0.75f32.sqrt(), 0.0, 0.5).unwrap(),
1048 // epsilon = 0.000001
1049 // );
1050 // assert_relative_eq!(
1051 // Dir3A::Z.slerp(Dir3A::Y, 2.0 / 3.0),
1052 // Dir3A::from_xyz(0.0, 0.75f32.sqrt(), 0.5).unwrap()
1053 // );
1054 }
1055
1056 #[test]
1057 fn dir3a_renorm() {
1058 // Evil denormalized quaternion
1059 let rot3 = Quat::from_euler(glam::EulerRot::XYZ, 1.0, 2.0, 3.0) * (1.0 + 1e-5);
1060 let mut dir_a = Dir3A::X;
1061 let mut dir_b = Dir3A::X;
1062
1063 // We test that renormalizing an already normalized dir doesn't do anything
1064 // assert_relative_eq!(dir_b, dir_b.fast_renormalize(), epsilon = 0.000001);
1065
1066 for _ in 0..50 {
1067 dir_a = rot3 * dir_a;
1068 dir_b = rot3 * dir_b;
1069 dir_b = dir_b.fast_renormalize();
1070 }
1071
1072 // `dir_a` should've gotten denormalized, meanwhile `dir_b` should stay normalized.
1073 assert!(
1074 !dir_a.is_normalized(),
1075 "Dernormalization doesn't work, test is faulty"
1076 );
1077 assert!(dir_b.is_normalized(), "Renormalisation did not work.");
1078 }
1079}