use crate::{ primitives::{Primitive2d, Primitive3d}, Quat, Rot2, Vec2, Vec3, Vec3A, }; use core::f32::consts::FRAC_1_SQRT_2; use derive_more::derive::Into; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// An error indicating that a direction is invalid. #[derive(Debug, PartialEq)] pub enum InvalidDirectionError { /// The length of the direction vector is zero or very close to zero. Zero, /// The length of the direction vector is `std::f32::INFINITY`. Infinite, /// The length of the direction vector is `NaN`. NaN, } impl InvalidDirectionError { /// Creates an [`InvalidDirectionError`] from the length of an invalid direction vector. pub fn from_length(length: f32) -> Self { if length.is_nan() { InvalidDirectionError::NaN } else if !length.is_finite() { // If the direction is non-finite but also not NaN, it must be infinite InvalidDirectionError::Infinite } else { // If the direction is invalid but neither NaN nor infinite, it must be zero InvalidDirectionError::Zero } } } impl core::fmt::Display for InvalidDirectionError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, "Direction can not be zero (or very close to zero), or non-finite." ) } } /// Checks that a vector with the given squared length is normalized. /// /// Warns for small error with a length threshold of approximately `1e-4`, /// and panics for large error with a length threshold of approximately `1e-2`. /// /// The format used for the logged warning is `"Warning: {warning} The length is {length}`, /// and similarly for the error. #[cfg(debug_assertions)] fn assert_is_normalized(message: &str, length_squared: f32) { let length_error_squared = (length_squared - 1.0).abs(); // Panic for large error and warn for slight error. if length_error_squared > 2e-2 || length_error_squared.is_nan() { // Length error is approximately 1e-2 or more. panic!("Error: {message} The length is {}.", length_squared.sqrt()); } else if length_error_squared > 2e-4 { // Length error is approximately 1e-4 or more. eprintln!( "Warning: {message} The length is {}.", length_squared.sqrt() ); } } /// A normalized vector pointing in a direction in 2D space #[deprecated( since = "0.14.0", note = "`Direction2d` has been renamed. Please use `Dir2` instead." )] pub type Direction2d = Dir2; /// A normalized vector pointing in a direction in 3D space #[deprecated( since = "0.14.0", note = "`Direction3d` has been renamed. Please use `Dir3` instead." )] pub type Direction3d = Dir3; /// A normalized vector pointing in a direction in 2D space #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr( all(feature = "serialize", feature = "bevy_reflect"), reflect(Serialize, Deserialize) )] #[doc(alias = "Direction2d")] pub struct Dir2(Vec2); impl Primitive2d for Dir2 {} impl Dir2 { /// A unit vector pointing along the positive X axis. pub const X: Self = Self(Vec2::X); /// A unit vector pointing along the positive Y axis. pub const Y: Self = Self(Vec2::Y); /// A unit vector pointing along the negative X axis. pub const NEG_X: Self = Self(Vec2::NEG_X); /// A unit vector pointing along the negative Y axis. pub const NEG_Y: Self = Self(Vec2::NEG_Y); /// The directional axes. pub const AXES: [Self; 2] = [Self::X, Self::Y]; /// The "north" direction, equivalent to [`Dir2::Y`]. pub const NORTH: Self = Self(Vec2::Y); /// The "south" direction, equivalent to [`Dir2::NEG_Y`]. pub const SOUTH: Self = Self(Vec2::NEG_Y); /// The "east" direction, equivalent to [`Dir2::X`]. pub const EAST: Self = Self(Vec2::X); /// The "west" direction, equivalent to [`Dir2::NEG_X`]. pub const WEST: Self = Self(Vec2::NEG_X); /// The "north-east" direction, between [`Dir2::NORTH`] and [`Dir2::EAST`]. pub const NORTH_EAST: Self = Self(Vec2::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2)); /// The "north-west" direction, between [`Dir2::NORTH`] and [`Dir2::WEST`]. pub const NORTH_WEST: Self = Self(Vec2::new(-FRAC_1_SQRT_2, FRAC_1_SQRT_2)); /// The "south-east" direction, between [`Dir2::SOUTH`] and [`Dir2::EAST`]. pub const SOUTH_EAST: Self = Self(Vec2::new(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)); /// The "south-west" direction, between [`Dir2::SOUTH`] and [`Dir2::WEST`]. pub const SOUTH_WEST: Self = Self(Vec2::new(-FRAC_1_SQRT_2, -FRAC_1_SQRT_2)); /// Create a direction from a finite, nonzero [`Vec2`], normalizing it. /// /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length /// of the given vector is zero (or very close to zero), infinite, or `NaN`. pub fn new(value: Vec2) -> Result { Self::new_and_length(value).map(|(dir, _)| dir) } /// Create a [`Dir2`] from a [`Vec2`] that is already normalized. /// /// # Warning /// /// `value` must be normalized, i.e its length must be `1.0`. pub fn new_unchecked(value: Vec2) -> Self { #[cfg(debug_assertions)] assert_is_normalized( "The vector given to `Dir2::new_unchecked` is not normalized.", value.length_squared(), ); Self(value) } /// Create a direction from a finite, nonzero [`Vec2`], normalizing it and /// also returning its original length. /// /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length /// of the given vector is zero (or very close to zero), infinite, or `NaN`. pub fn new_and_length(value: Vec2) -> Result<(Self, f32), InvalidDirectionError> { let length = value.length(); let direction = (length.is_finite() && length > 0.0).then_some(value / length); direction .map(|dir| (Self(dir), length)) .ok_or(InvalidDirectionError::from_length(length)) } /// Create a direction from its `x` and `y` components. /// /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length /// of the vector formed by the components is zero (or very close to zero), infinite, or `NaN`. pub fn from_xy(x: f32, y: f32) -> Result { Self::new(Vec2::new(x, y)) } /// Create a direction from its `x` and `y` components, assuming the resulting vector is normalized. /// /// # Warning /// /// The vector produced from `x` and `y` must be normalized, i.e its length must be `1.0`. pub fn from_xy_unchecked(x: f32, y: f32) -> Self { Self::new_unchecked(Vec2::new(x, y)) } /// Returns the inner [`Vec2`] pub const fn as_vec2(&self) -> Vec2 { self.0 } /// Performs a spherical linear interpolation between `self` and `rhs` /// based on the value `s`. /// /// This corresponds to interpolating between the two directions at a constant angular velocity. /// /// When `s == 0.0`, the result will be equal to `self`. /// When `s == 1.0`, the result will be equal to `rhs`. /// /// # Example /// /// ``` /// # use bevy_math::Dir2; /// # use approx::{assert_relative_eq, RelativeEq}; /// # /// let dir1 = Dir2::X; /// let dir2 = Dir2::Y; /// /// let result1 = dir1.slerp(dir2, 1.0 / 3.0); /// assert_relative_eq!(result1, Dir2::from_xy(0.75_f32.sqrt(), 0.5).unwrap()); /// /// let result2 = dir1.slerp(dir2, 0.5); /// assert_relative_eq!(result2, Dir2::from_xy(0.5_f32.sqrt(), 0.5_f32.sqrt()).unwrap()); /// ``` #[inline] pub fn slerp(self, rhs: Self, s: f32) -> Self { let angle = self.angle_to(rhs.0); Rot2::radians(angle * s) * self } /// Get the rotation that rotates this direction to `other`. #[inline] pub fn rotation_to(self, other: Self) -> Rot2 { // Rotate `self` to X-axis, then X-axis to `other`: other.rotation_from_x() * self.rotation_to_x() } /// Get the rotation that rotates `other` to this direction. #[inline] pub fn rotation_from(self, other: Self) -> Rot2 { other.rotation_to(self) } /// Get the rotation that rotates the X-axis to this direction. #[inline] pub fn rotation_from_x(self) -> Rot2 { Rot2::from_sin_cos(self.0.y, self.0.x) } /// Get the rotation that rotates this direction to the X-axis. #[inline] pub fn rotation_to_x(self) -> Rot2 { // (This is cheap, it just negates one component.) self.rotation_from_x().inverse() } /// Get the rotation that rotates the Y-axis to this direction. #[inline] pub fn rotation_from_y(self) -> Rot2 { // `x <- y`, `y <- -x` correspond to rotating clockwise by pi/2; // this transforms the Y-axis into the X-axis, maintaining the relative position // of our direction. Then we just use the same technique as `rotation_from_x`. Rot2::from_sin_cos(-self.0.x, self.0.y) } /// Get the rotation that rotates this direction to the Y-axis. #[inline] pub fn rotation_to_y(self) -> Rot2 { self.rotation_from_y().inverse() } /// Returns `self` after an approximate normalization, assuming the value is already nearly normalized. /// Useful for preventing numerical error accumulation. /// See [`Dir3::fast_renormalize`] for an example of when such error accumulation might occur. #[inline] pub fn fast_renormalize(self) -> Self { let length_squared = self.0.length_squared(); // Based on a Taylor approximation of the inverse square root, see [`Dir3::fast_renormalize`] for more details. Self(self * (0.5 * (3.0 - length_squared))) } } impl TryFrom for Dir2 { type Error = InvalidDirectionError; fn try_from(value: Vec2) -> Result { Self::new(value) } } impl From for Vec2 { fn from(value: Dir2) -> Self { value.as_vec2() } } impl core::ops::Deref for Dir2 { type Target = Vec2; fn deref(&self) -> &Self::Target { &self.0 } } impl core::ops::Neg for Dir2 { type Output = Self; fn neg(self) -> Self::Output { Self(-self.0) } } impl core::ops::Mul for Dir2 { type Output = Vec2; fn mul(self, rhs: f32) -> Self::Output { self.0 * rhs } } impl core::ops::Mul for f32 { type Output = Vec2; fn mul(self, rhs: Dir2) -> Self::Output { self * rhs.0 } } impl core::ops::Mul for Rot2 { type Output = Dir2; /// Rotates the [`Dir2`] using a [`Rot2`]. fn mul(self, direction: Dir2) -> Self::Output { let rotated = self * *direction; #[cfg(debug_assertions)] assert_is_normalized( "`Dir2` is denormalized after rotation.", rotated.length_squared(), ); Dir2(rotated) } } #[cfg(any(feature = "approx", test))] impl approx::AbsDiffEq for Dir2 { type Epsilon = f32; fn default_epsilon() -> f32 { f32::EPSILON } fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool { self.as_ref().abs_diff_eq(other.as_ref(), epsilon) } } #[cfg(any(feature = "approx", test))] impl approx::RelativeEq for Dir2 { fn default_max_relative() -> f32 { f32::EPSILON } fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool { self.as_ref() .relative_eq(other.as_ref(), epsilon, max_relative) } } #[cfg(any(feature = "approx", test))] impl approx::UlpsEq for Dir2 { fn default_max_ulps() -> u32 { 4 } fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool { self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps) } } /// A normalized vector pointing in a direction in 3D space #[derive(Clone, Copy, Debug, PartialEq, Into)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr( all(feature = "serialize", feature = "bevy_reflect"), reflect(Serialize, Deserialize) )] #[doc(alias = "Direction3d")] pub struct Dir3(Vec3); impl Primitive3d for Dir3 {} impl Dir3 { /// A unit vector pointing along the positive X axis. pub const X: Self = Self(Vec3::X); /// A unit vector pointing along the positive Y axis. pub const Y: Self = Self(Vec3::Y); /// A unit vector pointing along the positive Z axis. pub const Z: Self = Self(Vec3::Z); /// A unit vector pointing along the negative X axis. pub const NEG_X: Self = Self(Vec3::NEG_X); /// A unit vector pointing along the negative Y axis. pub const NEG_Y: Self = Self(Vec3::NEG_Y); /// A unit vector pointing along the negative Z axis. pub const NEG_Z: Self = Self(Vec3::NEG_Z); /// The directional axes. pub const AXES: [Self; 3] = [Self::X, Self::Y, Self::Z]; /// Create a direction from a finite, nonzero [`Vec3`], normalizing it. /// /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length /// of the given vector is zero (or very close to zero), infinite, or `NaN`. pub fn new(value: Vec3) -> Result { Self::new_and_length(value).map(|(dir, _)| dir) } /// Create a [`Dir3`] from a [`Vec3`] that is already normalized. /// /// # Warning /// /// `value` must be normalized, i.e its length must be `1.0`. pub fn new_unchecked(value: Vec3) -> Self { #[cfg(debug_assertions)] assert_is_normalized( "The vector given to `Dir3::new_unchecked` is not normalized.", value.length_squared(), ); Self(value) } /// Create a direction from a finite, nonzero [`Vec3`], normalizing it and /// also returning its original length. /// /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length /// of the given vector is zero (or very close to zero), infinite, or `NaN`. pub fn new_and_length(value: Vec3) -> Result<(Self, f32), InvalidDirectionError> { let length = value.length(); let direction = (length.is_finite() && length > 0.0).then_some(value / length); direction .map(|dir| (Self(dir), length)) .ok_or(InvalidDirectionError::from_length(length)) } /// Create a direction from its `x`, `y`, and `z` components. /// /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length /// of the vector formed by the components is zero (or very close to zero), infinite, or `NaN`. pub fn from_xyz(x: f32, y: f32, z: f32) -> Result { Self::new(Vec3::new(x, y, z)) } /// Create a direction from its `x`, `y`, and `z` components, assuming the resulting vector is normalized. /// /// # Warning /// /// The vector produced from `x`, `y`, and `z` must be normalized, i.e its length must be `1.0`. pub fn from_xyz_unchecked(x: f32, y: f32, z: f32) -> Self { Self::new_unchecked(Vec3::new(x, y, z)) } /// Returns the inner [`Vec3`] pub const fn as_vec3(&self) -> Vec3 { self.0 } /// Performs a spherical linear interpolation between `self` and `rhs` /// based on the value `s`. /// /// This corresponds to interpolating between the two directions at a constant angular velocity. /// /// When `s == 0.0`, the result will be equal to `self`. /// When `s == 1.0`, the result will be equal to `rhs`. /// /// # Example /// /// ``` /// # use bevy_math::Dir3; /// # use approx::{assert_relative_eq, RelativeEq}; /// # /// let dir1 = Dir3::X; /// let dir2 = Dir3::Y; /// /// let result1 = dir1.slerp(dir2, 1.0 / 3.0); /// assert_relative_eq!( /// result1, /// Dir3::from_xyz(0.75_f32.sqrt(), 0.5, 0.0).unwrap(), /// epsilon = 0.000001 /// ); /// /// let result2 = dir1.slerp(dir2, 0.5); /// assert_relative_eq!(result2, Dir3::from_xyz(0.5_f32.sqrt(), 0.5_f32.sqrt(), 0.0).unwrap()); /// ``` #[inline] pub fn slerp(self, rhs: Self, s: f32) -> Self { let quat = Quat::IDENTITY.slerp(Quat::from_rotation_arc(self.0, rhs.0), s); Dir3(quat.mul_vec3(self.0)) } /// Returns `self` after an approximate normalization, assuming the value is already nearly normalized. /// Useful for preventing numerical error accumulation. /// /// # Example /// The following seemingly benign code would start accumulating errors over time, /// leading to `dir` eventually not being normalized anymore. /// ``` /// # use bevy_math::prelude::*; /// # let N: usize = 200; /// let mut dir = Dir3::X; /// let quaternion = Quat::from_euler(EulerRot::XYZ, 1.0, 2.0, 3.0); /// for i in 0..N { /// dir = quaternion * dir; /// } /// ``` /// Instead, do the following. /// ``` /// # use bevy_math::prelude::*; /// # let N: usize = 200; /// let mut dir = Dir3::X; /// let quaternion = Quat::from_euler(EulerRot::XYZ, 1.0, 2.0, 3.0); /// for i in 0..N { /// dir = quaternion * dir; /// dir = dir.fast_renormalize(); /// } /// ``` #[inline] pub fn fast_renormalize(self) -> Self { // We numerically approximate the inverse square root by a Taylor series around 1 // As we expect the error (x := length_squared - 1) to be small // inverse_sqrt(length_squared) = (1 + x)^(-1/2) = 1 - 1/2 x + O(x²) // inverse_sqrt(length_squared) ≈ 1 - 1/2 (length_squared - 1) = 1/2 (3 - length_squared) // Iterative calls to this method quickly converge to a normalized value, // so long as the denormalization is not large ~ O(1/10). // One iteration can be described as: // l_sq <- l_sq * (1 - 1/2 (l_sq - 1))²; // Rewriting in terms of the error x: // 1 + x <- (1 + x) * (1 - 1/2 x)² // 1 + x <- (1 + x) * (1 - x + 1/4 x²) // 1 + x <- 1 - x + 1/4 x² + x - x² + 1/4 x³ // x <- -1/4 x² (3 - x) // If the error is small, say in a range of (-1/2, 1/2), then: // |-1/4 x² (3 - x)| <= (3/4 + 1/4 * |x|) * x² <= (3/4 + 1/4 * 1/2) * x² < x² < 1/2 x // Therefore the sequence of iterates converges to 0 error as a second order method. let length_squared = self.0.length_squared(); Self(self * (0.5 * (3.0 - length_squared))) } } impl TryFrom for Dir3 { type Error = InvalidDirectionError; fn try_from(value: Vec3) -> Result { Self::new(value) } } impl core::ops::Deref for Dir3 { type Target = Vec3; fn deref(&self) -> &Self::Target { &self.0 } } impl core::ops::Neg for Dir3 { type Output = Self; fn neg(self) -> Self::Output { Self(-self.0) } } impl core::ops::Mul for Dir3 { type Output = Vec3; fn mul(self, rhs: f32) -> Self::Output { self.0 * rhs } } impl core::ops::Mul for f32 { type Output = Vec3; fn mul(self, rhs: Dir3) -> Self::Output { self * rhs.0 } } impl core::ops::Mul for Quat { type Output = Dir3; /// Rotates the [`Dir3`] using a [`Quat`]. fn mul(self, direction: Dir3) -> Self::Output { let rotated = self * *direction; #[cfg(debug_assertions)] assert_is_normalized( "`Dir3` is denormalized after rotation.", rotated.length_squared(), ); Dir3(rotated) } } #[cfg(feature = "approx")] impl approx::AbsDiffEq for Dir3 { type Epsilon = f32; fn default_epsilon() -> f32 { f32::EPSILON } fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool { self.as_ref().abs_diff_eq(other.as_ref(), epsilon) } } #[cfg(feature = "approx")] impl approx::RelativeEq for Dir3 { fn default_max_relative() -> f32 { f32::EPSILON } fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool { self.as_ref() .relative_eq(other.as_ref(), epsilon, max_relative) } } #[cfg(feature = "approx")] impl approx::UlpsEq for Dir3 { fn default_max_ulps() -> u32 { 4 } fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool { self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps) } } /// A normalized SIMD vector pointing in a direction in 3D space. /// /// This type stores a 16 byte aligned [`Vec3A`]. /// This may or may not be faster than [`Dir3`]: make sure to benchmark! #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr( all(feature = "serialize", feature = "bevy_reflect"), reflect(Serialize, Deserialize) )] #[doc(alias = "Direction3dA")] pub struct Dir3A(Vec3A); impl Primitive3d for Dir3A {} impl Dir3A { /// A unit vector pointing along the positive X axis. pub const X: Self = Self(Vec3A::X); /// A unit vector pointing along the positive Y axis. pub const Y: Self = Self(Vec3A::Y); /// A unit vector pointing along the positive Z axis. pub const Z: Self = Self(Vec3A::Z); /// A unit vector pointing along the negative X axis. pub const NEG_X: Self = Self(Vec3A::NEG_X); /// A unit vector pointing along the negative Y axis. pub const NEG_Y: Self = Self(Vec3A::NEG_Y); /// A unit vector pointing along the negative Z axis. pub const NEG_Z: Self = Self(Vec3A::NEG_Z); /// The directional axes. pub const AXES: [Self; 3] = [Self::X, Self::Y, Self::Z]; /// Create a direction from a finite, nonzero [`Vec3A`], normalizing it. /// /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length /// of the given vector is zero (or very close to zero), infinite, or `NaN`. pub fn new(value: Vec3A) -> Result { Self::new_and_length(value).map(|(dir, _)| dir) } /// Create a [`Dir3A`] from a [`Vec3A`] that is already normalized. /// /// # Warning /// /// `value` must be normalized, i.e its length must be `1.0`. pub fn new_unchecked(value: Vec3A) -> Self { #[cfg(debug_assertions)] assert_is_normalized( "The vector given to `Dir3A::new_unchecked` is not normalized.", value.length_squared(), ); Self(value) } /// Create a direction from a finite, nonzero [`Vec3A`], normalizing it and /// also returning its original length. /// /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length /// of the given vector is zero (or very close to zero), infinite, or `NaN`. pub fn new_and_length(value: Vec3A) -> Result<(Self, f32), InvalidDirectionError> { let length = value.length(); let direction = (length.is_finite() && length > 0.0).then_some(value / length); direction .map(|dir| (Self(dir), length)) .ok_or(InvalidDirectionError::from_length(length)) } /// Create a direction from its `x`, `y`, and `z` components. /// /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length /// of the vector formed by the components is zero (or very close to zero), infinite, or `NaN`. pub fn from_xyz(x: f32, y: f32, z: f32) -> Result { Self::new(Vec3A::new(x, y, z)) } /// Create a direction from its `x`, `y`, and `z` components, assuming the resulting vector is normalized. /// /// # Warning /// /// The vector produced from `x`, `y`, and `z` must be normalized, i.e its length must be `1.0`. pub fn from_xyz_unchecked(x: f32, y: f32, z: f32) -> Self { Self::new_unchecked(Vec3A::new(x, y, z)) } /// Returns the inner [`Vec3A`] pub const fn as_vec3a(&self) -> Vec3A { self.0 } /// Performs a spherical linear interpolation between `self` and `rhs` /// based on the value `s`. /// /// This corresponds to interpolating between the two directions at a constant angular velocity. /// /// When `s == 0.0`, the result will be equal to `self`. /// When `s == 1.0`, the result will be equal to `rhs`. /// /// # Example /// /// ``` /// # use bevy_math::Dir3A; /// # use approx::{assert_relative_eq, RelativeEq}; /// # /// let dir1 = Dir3A::X; /// let dir2 = Dir3A::Y; /// /// let result1 = dir1.slerp(dir2, 1.0 / 3.0); /// assert_relative_eq!( /// result1, /// Dir3A::from_xyz(0.75_f32.sqrt(), 0.5, 0.0).unwrap(), /// epsilon = 0.000001 /// ); /// /// let result2 = dir1.slerp(dir2, 0.5); /// assert_relative_eq!(result2, Dir3A::from_xyz(0.5_f32.sqrt(), 0.5_f32.sqrt(), 0.0).unwrap()); /// ``` #[inline] pub fn slerp(self, rhs: Self, s: f32) -> Self { let quat = Quat::IDENTITY.slerp( Quat::from_rotation_arc(Vec3::from(self.0), Vec3::from(rhs.0)), s, ); Dir3A(quat.mul_vec3a(self.0)) } /// Returns `self` after an approximate normalization, assuming the value is already nearly normalized. /// Useful for preventing numerical error accumulation. /// /// See [`Dir3::fast_renormalize`] for an example of when such error accumulation might occur. #[inline] pub fn fast_renormalize(self) -> Self { let length_squared = self.0.length_squared(); // Based on a Taylor approximation of the inverse square root, see [`Dir3::fast_renormalize`] for more details. Self(self * (0.5 * (3.0 - length_squared))) } } impl From for Dir3A { fn from(value: Dir3) -> Self { Self(value.0.into()) } } impl From for Dir3 { fn from(value: Dir3A) -> Self { Self(value.0.into()) } } impl TryFrom for Dir3A { type Error = InvalidDirectionError; fn try_from(value: Vec3A) -> Result { Self::new(value) } } impl From for Vec3A { fn from(value: Dir3A) -> Self { value.0 } } impl core::ops::Deref for Dir3A { type Target = Vec3A; fn deref(&self) -> &Self::Target { &self.0 } } impl core::ops::Neg for Dir3A { type Output = Self; fn neg(self) -> Self::Output { Self(-self.0) } } impl core::ops::Mul for Dir3A { type Output = Vec3A; fn mul(self, rhs: f32) -> Self::Output { self.0 * rhs } } impl core::ops::Mul for f32 { type Output = Vec3A; fn mul(self, rhs: Dir3A) -> Self::Output { self * rhs.0 } } impl core::ops::Mul for Quat { type Output = Dir3A; /// Rotates the [`Dir3A`] using a [`Quat`]. fn mul(self, direction: Dir3A) -> Self::Output { let rotated = self * *direction; #[cfg(debug_assertions)] assert_is_normalized( "`Dir3A` is denormalized after rotation.", rotated.length_squared(), ); Dir3A(rotated) } } #[cfg(feature = "approx")] impl approx::AbsDiffEq for Dir3A { type Epsilon = f32; fn default_epsilon() -> f32 { f32::EPSILON } fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool { self.as_ref().abs_diff_eq(other.as_ref(), epsilon) } } #[cfg(feature = "approx")] impl approx::RelativeEq for Dir3A { fn default_max_relative() -> f32 { f32::EPSILON } fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool { self.as_ref() .relative_eq(other.as_ref(), epsilon, max_relative) } } #[cfg(feature = "approx")] impl approx::UlpsEq for Dir3A { fn default_max_ulps() -> u32 { 4 } fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool { self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps) } } #[cfg(test)] mod tests { use crate::ops; use super::*; use approx::assert_relative_eq; #[test] fn dir2_creation() { assert_eq!(Dir2::new(Vec2::X * 12.5), Ok(Dir2::X)); assert_eq!( Dir2::new(Vec2::new(0.0, 0.0)), Err(InvalidDirectionError::Zero) ); assert_eq!( Dir2::new(Vec2::new(f32::INFINITY, 0.0)), Err(InvalidDirectionError::Infinite) ); assert_eq!( Dir2::new(Vec2::new(f32::NEG_INFINITY, 0.0)), Err(InvalidDirectionError::Infinite) ); assert_eq!( Dir2::new(Vec2::new(f32::NAN, 0.0)), Err(InvalidDirectionError::NaN) ); assert_eq!(Dir2::new_and_length(Vec2::X * 6.5), Ok((Dir2::X, 6.5))); } #[test] fn dir2_slerp() { assert_relative_eq!( Dir2::X.slerp(Dir2::Y, 0.5), Dir2::from_xy(0.5_f32.sqrt(), 0.5_f32.sqrt()).unwrap() ); assert_eq!(Dir2::Y.slerp(Dir2::X, 0.0), Dir2::Y); assert_relative_eq!(Dir2::X.slerp(Dir2::Y, 1.0), Dir2::Y); assert_relative_eq!( Dir2::Y.slerp(Dir2::X, 1.0 / 3.0), Dir2::from_xy(0.5, 0.75_f32.sqrt()).unwrap() ); assert_relative_eq!( Dir2::X.slerp(Dir2::Y, 2.0 / 3.0), Dir2::from_xy(0.5, 0.75_f32.sqrt()).unwrap() ); } #[test] fn dir2_to_rotation2d() { assert_relative_eq!(Dir2::EAST.rotation_to(Dir2::NORTH_EAST), Rot2::FRAC_PI_4); assert_relative_eq!(Dir2::NORTH.rotation_from(Dir2::NORTH_EAST), Rot2::FRAC_PI_4); assert_relative_eq!(Dir2::SOUTH.rotation_to_x(), Rot2::FRAC_PI_2); assert_relative_eq!(Dir2::SOUTH.rotation_to_y(), Rot2::PI); assert_relative_eq!(Dir2::NORTH_WEST.rotation_from_x(), Rot2::degrees(135.0)); assert_relative_eq!(Dir2::NORTH_WEST.rotation_from_y(), Rot2::FRAC_PI_4); } #[test] fn dir2_renorm() { // Evil denormalized Rot2 let (sin, cos) = ops::sin_cos(1.0_f32); let rot2 = Rot2::from_sin_cos(sin * (1.0 + 1e-5), cos * (1.0 + 1e-5)); let mut dir_a = Dir2::X; let mut dir_b = Dir2::X; // We test that renormalizing an already normalized dir doesn't do anything assert_relative_eq!(dir_b, dir_b.fast_renormalize(), epsilon = 0.000001); for _ in 0..50 { dir_a = rot2 * dir_a; dir_b = rot2 * dir_b; dir_b = dir_b.fast_renormalize(); } // `dir_a` should've gotten denormalized, meanwhile `dir_b` should stay normalized. assert!( !dir_a.is_normalized(), "Dernormalization doesn't work, test is faulty" ); assert!(dir_b.is_normalized(), "Renormalisation did not work."); } #[test] fn dir3_creation() { assert_eq!(Dir3::new(Vec3::X * 12.5), Ok(Dir3::X)); assert_eq!( Dir3::new(Vec3::new(0.0, 0.0, 0.0)), Err(InvalidDirectionError::Zero) ); assert_eq!( Dir3::new(Vec3::new(f32::INFINITY, 0.0, 0.0)), Err(InvalidDirectionError::Infinite) ); assert_eq!( Dir3::new(Vec3::new(f32::NEG_INFINITY, 0.0, 0.0)), Err(InvalidDirectionError::Infinite) ); assert_eq!( Dir3::new(Vec3::new(f32::NAN, 0.0, 0.0)), Err(InvalidDirectionError::NaN) ); assert_eq!(Dir3::new_and_length(Vec3::X * 6.5), Ok((Dir3::X, 6.5))); // Test rotation assert!( (Quat::from_rotation_z(core::f32::consts::FRAC_PI_2) * Dir3::X) .abs_diff_eq(Vec3::Y, 10e-6) ); } #[test] fn dir3_slerp() { assert_relative_eq!( Dir3::X.slerp(Dir3::Y, 0.5), Dir3::from_xyz(0.5f32.sqrt(), 0.5f32.sqrt(), 0.0).unwrap() ); assert_relative_eq!(Dir3::Y.slerp(Dir3::Z, 0.0), Dir3::Y); assert_relative_eq!(Dir3::Z.slerp(Dir3::X, 1.0), Dir3::X, epsilon = 0.000001); assert_relative_eq!( Dir3::X.slerp(Dir3::Z, 1.0 / 3.0), Dir3::from_xyz(0.75f32.sqrt(), 0.0, 0.5).unwrap(), epsilon = 0.000001 ); assert_relative_eq!( Dir3::Z.slerp(Dir3::Y, 2.0 / 3.0), Dir3::from_xyz(0.0, 0.75f32.sqrt(), 0.5).unwrap() ); } #[test] fn dir3_renorm() { // Evil denormalized quaternion let rot3 = Quat::from_euler(glam::EulerRot::XYZ, 1.0, 2.0, 3.0) * (1.0 + 1e-5); let mut dir_a = Dir3::X; let mut dir_b = Dir3::X; // We test that renormalizing an already normalized dir doesn't do anything assert_relative_eq!(dir_b, dir_b.fast_renormalize(), epsilon = 0.000001); for _ in 0..50 { dir_a = rot3 * dir_a; dir_b = rot3 * dir_b; dir_b = dir_b.fast_renormalize(); } // `dir_a` should've gotten denormalized, meanwhile `dir_b` should stay normalized. assert!( !dir_a.is_normalized(), "Dernormalization doesn't work, test is faulty" ); assert!(dir_b.is_normalized(), "Renormalisation did not work."); } #[test] fn dir3a_creation() { assert_eq!(Dir3A::new(Vec3A::X * 12.5), Ok(Dir3A::X)); assert_eq!( Dir3A::new(Vec3A::new(0.0, 0.0, 0.0)), Err(InvalidDirectionError::Zero) ); assert_eq!( Dir3A::new(Vec3A::new(f32::INFINITY, 0.0, 0.0)), Err(InvalidDirectionError::Infinite) ); assert_eq!( Dir3A::new(Vec3A::new(f32::NEG_INFINITY, 0.0, 0.0)), Err(InvalidDirectionError::Infinite) ); assert_eq!( Dir3A::new(Vec3A::new(f32::NAN, 0.0, 0.0)), Err(InvalidDirectionError::NaN) ); assert_eq!(Dir3A::new_and_length(Vec3A::X * 6.5), Ok((Dir3A::X, 6.5))); // Test rotation assert!( (Quat::from_rotation_z(core::f32::consts::FRAC_PI_2) * Dir3A::X) .abs_diff_eq(Vec3A::Y, 10e-6) ); } #[test] fn dir3a_slerp() { assert_relative_eq!( Dir3A::X.slerp(Dir3A::Y, 0.5), Dir3A::from_xyz(0.5f32.sqrt(), 0.5f32.sqrt(), 0.0).unwrap() ); assert_relative_eq!(Dir3A::Y.slerp(Dir3A::Z, 0.0), Dir3A::Y); assert_relative_eq!(Dir3A::Z.slerp(Dir3A::X, 1.0), Dir3A::X, epsilon = 0.000001); assert_relative_eq!( Dir3A::X.slerp(Dir3A::Z, 1.0 / 3.0), Dir3A::from_xyz(0.75f32.sqrt(), 0.0, 0.5).unwrap(), epsilon = 0.000001 ); assert_relative_eq!( Dir3A::Z.slerp(Dir3A::Y, 2.0 / 3.0), Dir3A::from_xyz(0.0, 0.75f32.sqrt(), 0.5).unwrap() ); } #[test] fn dir3a_renorm() { // Evil denormalized quaternion let rot3 = Quat::from_euler(glam::EulerRot::XYZ, 1.0, 2.0, 3.0) * (1.0 + 1e-5); let mut dir_a = Dir3A::X; let mut dir_b = Dir3A::X; // We test that renormalizing an already normalized dir doesn't do anything assert_relative_eq!(dir_b, dir_b.fast_renormalize(), epsilon = 0.000001); for _ in 0..50 { dir_a = rot3 * dir_a; dir_b = rot3 * dir_b; dir_b = dir_b.fast_renormalize(); } // `dir_a` should've gotten denormalized, meanwhile `dir_b` should stay normalized. assert!( !dir_a.is_normalized(), "Dernormalization doesn't work, test is faulty" ); assert!(dir_b.is_normalized(), "Renormalisation did not work."); } }