use crate::{
primitives::{Primitive2d, Primitive3d},
Quat, Rot2, Vec2, Vec3, Vec3A, Vec4,
};
use core::f32::consts::FRAC_1_SQRT_2;
use core::fmt;
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};
#[cfg(all(debug_assertions, feature = "std"))]
use std::eprintln;
use thiserror::Error;
#[derive(Debug, PartialEq, Error)]
pub enum InvalidDirectionError {
#[error("The length of the direction vector is zero or very close to zero")]
Zero,
#[error("The length of the direction vector is `std::f32::INFINITY`")]
Infinite,
#[error("The length of the direction vector is `NaN`")]
NaN,
}
impl InvalidDirectionError {
pub fn from_length(length: f32) -> Self {
if length.is_nan() {
InvalidDirectionError::NaN
} else if !length.is_finite() {
InvalidDirectionError::Infinite
} else {
InvalidDirectionError::Zero
}
}
}
#[cfg(debug_assertions)]
fn assert_is_normalized(message: &str, length_squared: f32) {
use crate::ops;
let length_error_squared = ops::abs(length_squared - 1.0);
if length_error_squared > 2e-2 || length_error_squared.is_nan() {
panic!(
"Error: {message} The length is {}.",
ops::sqrt(length_squared)
);
} else if length_error_squared > 2e-4 {
#[cfg(feature = "std")]
#[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")]
{
eprintln!(
"Warning: {message} The length is {}.",
ops::sqrt(length_squared)
);
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[doc(alias = "Direction2d")]
pub struct Dir2(Vec2);
impl Primitive2d for Dir2 {}
impl Dir2 {
pub const X: Self = Self(Vec2::X);
pub const Y: Self = Self(Vec2::Y);
pub const NEG_X: Self = Self(Vec2::NEG_X);
pub const NEG_Y: Self = Self(Vec2::NEG_Y);
pub const AXES: [Self; 2] = [Self::X, Self::Y];
pub const NORTH: Self = Self(Vec2::Y);
pub const SOUTH: Self = Self(Vec2::NEG_Y);
pub const EAST: Self = Self(Vec2::X);
pub const WEST: Self = Self(Vec2::NEG_X);
pub const NORTH_EAST: Self = Self(Vec2::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2));
pub const NORTH_WEST: Self = Self(Vec2::new(-FRAC_1_SQRT_2, FRAC_1_SQRT_2));
pub const SOUTH_EAST: Self = Self(Vec2::new(FRAC_1_SQRT_2, -FRAC_1_SQRT_2));
pub const SOUTH_WEST: Self = Self(Vec2::new(-FRAC_1_SQRT_2, -FRAC_1_SQRT_2));
pub fn new(value: Vec2) -> Result<Self, InvalidDirectionError> {
Self::new_and_length(value).map(|(dir, _)| dir)
}
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)
}
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))
}
pub fn from_xy(x: f32, y: f32) -> Result<Self, InvalidDirectionError> {
Self::new(Vec2::new(x, y))
}
pub fn from_xy_unchecked(x: f32, y: f32) -> Self {
Self::new_unchecked(Vec2::new(x, y))
}
pub const fn as_vec2(&self) -> Vec2 {
self.0
}
#[inline]
pub fn slerp(self, rhs: Self, s: f32) -> Self {
let angle = self.angle_to(rhs.0);
Rot2::radians(angle * s) * self
}
#[inline]
pub fn rotation_to(self, other: Self) -> Rot2 {
other.rotation_from_x() * self.rotation_to_x()
}
#[inline]
pub fn rotation_from(self, other: Self) -> Rot2 {
other.rotation_to(self)
}
#[inline]
pub fn rotation_from_x(self) -> Rot2 {
Rot2::from_sin_cos(self.0.y, self.0.x)
}
#[inline]
pub fn rotation_to_x(self) -> Rot2 {
self.rotation_from_x().inverse()
}
#[inline]
pub fn rotation_from_y(self) -> Rot2 {
Rot2::from_sin_cos(-self.0.x, self.0.y)
}
#[inline]
pub fn rotation_to_y(self) -> Rot2 {
self.rotation_from_y().inverse()
}
#[inline]
pub fn fast_renormalize(self) -> Self {
let length_squared = self.0.length_squared();
Self(self * (0.5 * (3.0 - length_squared)))
}
}
impl TryFrom<Vec2> for Dir2 {
type Error = InvalidDirectionError;
fn try_from(value: Vec2) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<Dir2> 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<f32> for Dir2 {
type Output = Vec2;
fn mul(self, rhs: f32) -> Self::Output {
self.0 * rhs
}
}
impl core::ops::Mul<Dir2> for f32 {
type Output = Vec2;
fn mul(self, rhs: Dir2) -> Self::Output {
self * rhs.0
}
}
impl core::ops::Mul<Dir2> for Rot2 {
type Output = Dir2;
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)
}
}
impl fmt::Display for Dir2 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[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)
}
}
#[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, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[doc(alias = "Direction3d")]
pub struct Dir3(Vec3);
impl Primitive3d for Dir3 {}
impl Dir3 {
pub const X: Self = Self(Vec3::X);
pub const Y: Self = Self(Vec3::Y);
pub const Z: Self = Self(Vec3::Z);
pub const NEG_X: Self = Self(Vec3::NEG_X);
pub const NEG_Y: Self = Self(Vec3::NEG_Y);
pub const NEG_Z: Self = Self(Vec3::NEG_Z);
pub const AXES: [Self; 3] = [Self::X, Self::Y, Self::Z];
pub fn new(value: Vec3) -> Result<Self, InvalidDirectionError> {
Self::new_and_length(value).map(|(dir, _)| dir)
}
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)
}
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))
}
pub fn from_xyz(x: f32, y: f32, z: f32) -> Result<Self, InvalidDirectionError> {
Self::new(Vec3::new(x, y, z))
}
pub fn from_xyz_unchecked(x: f32, y: f32, z: f32) -> Self {
Self::new_unchecked(Vec3::new(x, y, z))
}
pub const fn as_vec3(&self) -> Vec3 {
self.0
}
#[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))
}
#[inline]
pub fn fast_renormalize(self) -> Self {
let length_squared = self.0.length_squared();
Self(self * (0.5 * (3.0 - length_squared)))
}
}
impl TryFrom<Vec3> for Dir3 {
type Error = InvalidDirectionError;
fn try_from(value: Vec3) -> Result<Self, Self::Error> {
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<f32> for Dir3 {
type Output = Vec3;
fn mul(self, rhs: f32) -> Self::Output {
self.0 * rhs
}
}
impl core::ops::Mul<Dir3> for f32 {
type Output = Vec3;
fn mul(self, rhs: Dir3) -> Self::Output {
self * rhs.0
}
}
impl core::ops::Mul<Dir3> for Quat {
type Output = Dir3;
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)
}
}
impl fmt::Display for Dir3 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[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)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[doc(alias = "Direction3dA")]
pub struct Dir3A(Vec3A);
impl Primitive3d for Dir3A {}
impl Dir3A {
pub const X: Self = Self(Vec3A::X);
pub const Y: Self = Self(Vec3A::Y);
pub const Z: Self = Self(Vec3A::Z);
pub const NEG_X: Self = Self(Vec3A::NEG_X);
pub const NEG_Y: Self = Self(Vec3A::NEG_Y);
pub const NEG_Z: Self = Self(Vec3A::NEG_Z);
pub const AXES: [Self; 3] = [Self::X, Self::Y, Self::Z];
pub fn new(value: Vec3A) -> Result<Self, InvalidDirectionError> {
Self::new_and_length(value).map(|(dir, _)| dir)
}
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)
}
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))
}
pub fn from_xyz(x: f32, y: f32, z: f32) -> Result<Self, InvalidDirectionError> {
Self::new(Vec3A::new(x, y, z))
}
pub fn from_xyz_unchecked(x: f32, y: f32, z: f32) -> Self {
Self::new_unchecked(Vec3A::new(x, y, z))
}
pub const fn as_vec3a(&self) -> Vec3A {
self.0
}
#[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))
}
#[inline]
pub fn fast_renormalize(self) -> Self {
let length_squared = self.0.length_squared();
Self(self * (0.5 * (3.0 - length_squared)))
}
}
impl From<Dir3> for Dir3A {
fn from(value: Dir3) -> Self {
Self(value.0.into())
}
}
impl From<Dir3A> for Dir3 {
fn from(value: Dir3A) -> Self {
Self(value.0.into())
}
}
impl TryFrom<Vec3A> for Dir3A {
type Error = InvalidDirectionError;
fn try_from(value: Vec3A) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<Dir3A> 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<f32> for Dir3A {
type Output = Vec3A;
fn mul(self, rhs: f32) -> Self::Output {
self.0 * rhs
}
}
impl core::ops::Mul<Dir3A> for f32 {
type Output = Vec3A;
fn mul(self, rhs: Dir3A) -> Self::Output {
self * rhs.0
}
}
impl core::ops::Mul<Dir3A> for Quat {
type Output = Dir3A;
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)
}
}
impl fmt::Display for Dir3A {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[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)
}
}
#[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, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[doc(alias = "Direction4d")]
pub struct Dir4(Vec4);
impl Dir4 {
pub const X: Self = Self(Vec4::X);
pub const Y: Self = Self(Vec4::Y);
pub const Z: Self = Self(Vec4::Z);
pub const W: Self = Self(Vec4::W);
pub const NEG_X: Self = Self(Vec4::NEG_X);
pub const NEG_Y: Self = Self(Vec4::NEG_Y);
pub const NEG_Z: Self = Self(Vec4::NEG_Z);
pub const NEG_W: Self = Self(Vec4::NEG_W);
pub const AXES: [Self; 4] = [Self::X, Self::Y, Self::Z, Self::W];
pub fn new(value: Vec4) -> Result<Self, InvalidDirectionError> {
Self::new_and_length(value).map(|(dir, _)| dir)
}
pub fn new_unchecked(value: Vec4) -> Self {
#[cfg(debug_assertions)]
assert_is_normalized(
"The vector given to `Dir4::new_unchecked` is not normalized.",
value.length_squared(),
);
Self(value)
}
pub fn new_and_length(value: Vec4) -> 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))
}
pub fn from_xyzw(x: f32, y: f32, z: f32, w: f32) -> Result<Self, InvalidDirectionError> {
Self::new(Vec4::new(x, y, z, w))
}
pub fn from_xyzw_unchecked(x: f32, y: f32, z: f32, w: f32) -> Self {
Self::new_unchecked(Vec4::new(x, y, z, w))
}
pub const fn as_vec4(&self) -> Vec4 {
self.0
}
#[inline]
pub fn fast_renormalize(self) -> Self {
let length_squared = self.0.length_squared();
Self(self * (0.5 * (3.0 - length_squared)))
}
}
impl TryFrom<Vec4> for Dir4 {
type Error = InvalidDirectionError;
fn try_from(value: Vec4) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl core::ops::Deref for Dir4 {
type Target = Vec4;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl core::ops::Neg for Dir4 {
type Output = Self;
fn neg(self) -> Self::Output {
Self(-self.0)
}
}
impl core::ops::Mul<f32> for Dir4 {
type Output = Vec4;
fn mul(self, rhs: f32) -> Self::Output {
self.0 * rhs
}
}
impl core::ops::Mul<Dir4> for f32 {
type Output = Vec4;
fn mul(self, rhs: Dir4) -> Self::Output {
self * rhs.0
}
}
impl fmt::Display for Dir4 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(feature = "approx")]
impl approx::AbsDiffEq for Dir4 {
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 Dir4 {
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 Dir4 {
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)]
#[cfg(feature = "approx")]
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(ops::sqrt(0.5_f32), ops::sqrt(0.5_f32)).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, ops::sqrt(0.75_f32)).unwrap()
);
assert_relative_eq!(
Dir2::X.slerp(Dir2::Y, 2.0 / 3.0),
Dir2::from_xy(0.5, ops::sqrt(0.75_f32)).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() {
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;
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();
}
assert!(
!dir_a.is_normalized(),
"Denormalization 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)));
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(ops::sqrt(0.5f32), ops::sqrt(0.5f32), 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(ops::sqrt(0.75f32), 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, ops::sqrt(0.75f32), 0.5).unwrap()
);
}
#[test]
fn dir3_renorm() {
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;
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();
}
assert!(
!dir_a.is_normalized(),
"Denormalization 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)));
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(ops::sqrt(0.5f32), ops::sqrt(0.5f32), 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(ops::sqrt(0.75f32), 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, ops::sqrt(0.75f32), 0.5).unwrap()
);
}
#[test]
fn dir3a_renorm() {
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;
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();
}
assert!(
!dir_a.is_normalized(),
"Denormalization doesn't work, test is faulty"
);
assert!(dir_b.is_normalized(), "Renormalisation did not work.");
}
#[test]
fn dir4_creation() {
assert_eq!(Dir4::new(Vec4::X * 12.5), Ok(Dir4::X));
assert_eq!(
Dir4::new(Vec4::new(0.0, 0.0, 0.0, 0.0)),
Err(InvalidDirectionError::Zero)
);
assert_eq!(
Dir4::new(Vec4::new(f32::INFINITY, 0.0, 0.0, 0.0)),
Err(InvalidDirectionError::Infinite)
);
assert_eq!(
Dir4::new(Vec4::new(f32::NEG_INFINITY, 0.0, 0.0, 0.0)),
Err(InvalidDirectionError::Infinite)
);
assert_eq!(
Dir4::new(Vec4::new(f32::NAN, 0.0, 0.0, 0.0)),
Err(InvalidDirectionError::NaN)
);
assert_eq!(Dir4::new_and_length(Vec4::X * 6.5), Ok((Dir4::X, 6.5)));
}
#[test]
fn dir4_renorm() {
let mat4 = bevy_math::Mat4::from_quat(Quat::from_euler(glam::EulerRot::XYZ, 1.0, 2.0, 3.0))
* (1.0 + 1e-5);
let mut dir_a = Dir4::from_xyzw(1., 1., 0., 0.).unwrap();
let mut dir_b = Dir4::from_xyzw(1., 1., 0., 0.).unwrap();
assert_relative_eq!(dir_b, dir_b.fast_renormalize(), epsilon = 0.000001);
for _ in 0..50 {
dir_a = Dir4(mat4 * *dir_a);
dir_b = Dir4(mat4 * *dir_b);
dir_b = dir_b.fast_renormalize();
}
assert!(
!dir_a.is_normalized(),
"Denormalization doesn't work, test is faulty"
);
assert!(dir_b.is_normalized(), "Renormalisation did not work.");
}
}