use super::{Plane, Projection}; use rand::Rng; use serde::{Deserialize, Serialize}; use tracing::warn; use vek::*; /// Type representing a direction using Vec3 that is normalized and NaN free /// These properties are enforced actively via panics when `debug_assertions` is /// enabled #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(into = "SerdeDir")] #[serde(from = "SerdeDir")] pub struct Dir(Vec3); impl Default for Dir { fn default() -> Self { Self::forward() } } // Validate at Deserialization #[derive(Copy, Clone, Debug, Serialize, Deserialize)] struct SerdeDir(Vec3); impl From for Dir { fn from(dir: SerdeDir) -> Self { let dir = dir.0; if dir.map(f32::is_nan).reduce_or() { warn!( ?dir, "Deserialized dir containing NaNs, replacing with default" ); Default::default() } else if !dir.is_normalized() { warn!( ?dir, "Deserialized unnormalized dir, replacing with default" ); Default::default() } else { Self(dir) } } } impl From for SerdeDir { fn from(other: Dir) -> SerdeDir { SerdeDir(*other) } } /*pub enum TryFromVec3Error { ContainsNans, NotNormalized, } impl TryFrom> for Dir { type Error = TryFromVec3Error; fn try_from(v: Vec3) -> Result { if v.map(f32::is_nan).reduce_or() { Err(TryFromVec3Error::ContainsNans) } else { v.try_normalized() .map(|n| Self(n)) .ok_or(TryFromVec3Error::NotNormalized) } } }*/ impl Dir { pub fn new(dir: Vec3) -> Self { debug_assert!(!dir.map(f32::is_nan).reduce_or()); debug_assert!(dir.is_normalized()); Self(dir) } pub fn from_unnormalized(dirs: Vec3) -> Option { dirs.try_normalized().map(|dir| { #[cfg(debug_assertions)] { if dir.map(f32::is_nan).reduce_or() { panic!("{} => {}", dirs, dir); } } Self(dir) }) } /// Generates a random direction that has a z component of 0 pub fn random_2d(rng: &mut impl Rng) -> Self { Self::from_unnormalized(Vec3::new( rng.gen_range(-1.0..=1.0), rng.gen_range(-1.0..=1.0), 0.0, )) .unwrap_or(Self::new(Vec3::unit_x())) } pub fn slerp(from: Self, to: Self, factor: f32) -> Self { Self(slerp_normalized(from.0, to.0, factor)) } #[must_use] pub fn slerped_to(self, to: Self, factor: f32) -> Self { Self(slerp_normalized(self.0, to.0, factor)) } /// Note: this uses `from` if `to` is unnormalizable pub fn slerp_to_vec3(from: Self, to: Vec3, factor: f32) -> Self { Self(slerp_to_unnormalized(from.0, to, factor).unwrap_or_else(|e| e)) } pub fn rotation_between(&self, to: Self) -> Quaternion { Quaternion::::rotation_from_to_3d(self.0, to.0) } pub fn rotation(&self) -> Quaternion { Self::default().rotation_between(*self) } pub fn is_valid(&self) -> bool { !self.0.map(f32::is_nan).reduce_or() && self.is_normalized() } pub fn up() -> Self { Dir::new(Vec3::::unit_z()) } pub fn down() -> Self { -Dir::new(Vec3::::unit_z()) } pub fn left() -> Self { -Dir::new(Vec3::::unit_x()) } pub fn right() -> Self { Dir::new(Vec3::::unit_x()) } pub fn forward() -> Self { Dir::new(Vec3::::unit_y()) } pub fn back() -> Self { -Dir::new(Vec3::::unit_y()) } pub fn to_horizontal(self) -> Option { Self::from_unnormalized(self.xy().into()) } pub fn vec(&self) -> &Vec3 { &self.0 } pub fn to_vec(self) -> Vec3 { self.0 } } impl std::ops::Deref for Dir { type Target = Vec3; fn deref(&self) -> &Vec3 { &self.0 } } impl From for Vec3 { fn from(dir: Dir) -> Self { *dir } } impl Projection for Dir { type Output = Option; fn projected(self, plane: &Plane) -> Self::Output { Dir::from_unnormalized(plane.projection(*self)) } } impl Projection for Vec3 { type Output = Vec3; fn projected(self, dir: &Dir) -> Self::Output { let dir = **dir; self.dot(dir) * dir } } impl std::ops::Mul for Quaternion { type Output = Dir; fn mul(self, dir: Dir) -> Self::Output { Dir((self * *dir).normalized()) } } impl std::ops::Neg for Dir { type Output = Dir; fn neg(self) -> Dir { Dir::new(-self.0) } } /// Begone ye NaN's /// Slerp two `Vec3`s skipping the slerp if their directions are very close /// This avoids a case where `vek`s slerp produces NaN's /// Additionally, it avoids unnecessary calculations if they are near identical /// Assumes `from` and `to` are normalized and returns a normalized vector #[inline(always)] fn slerp_normalized(from: Vec3, to: Vec3, factor: f32) -> Vec3 { debug_assert!(!to.map(f32::is_nan).reduce_or()); debug_assert!(!from.map(f32::is_nan).reduce_or()); // Ensure from is normalized #[cfg(debug_assertions)] { let unnormalized = { let len_sq = from.magnitude_squared(); !(0.999..=1.001).contains(&len_sq) }; if unnormalized { panic!("Called slerp_normalized with unnormalized `from`: {}", from); } } // Ensure to is normalized #[cfg(debug_assertions)] { let unnormalized = { let len_sq = from.magnitude_squared(); !(0.999..=1.001).contains(&len_sq) }; if unnormalized { panic!("Called slerp_normalized with unnormalized `to`: {}", to); } } let dot = from.dot(to); if dot >= 1.0 - 1E-6 { // Close together, just use to return to; } let (from, to, factor) = if dot < -0.999 { // Not linearly independent (slerp will fail since it doesn't check for this) // Instead we will choose a midpoint and slerp from or to that depending on the // factor let mid_dir = if from.z.abs() > 0.999 { // If vec's lie along the z-axis default to (1, 0, 0) as midpoint Vec3::unit_x() } else { // Default to picking midpoint in the xy plane Vec3::new(from.y, -from.x, 0.0).normalized() }; if factor > 0.5 { (mid_dir, to, factor * 2.0 - 1.0) } else { (from, mid_dir, factor * 2.0) } } else { (from, to, factor) }; let slerped = Vec3::slerp(from, to, factor); let slerped_normalized = slerped.normalized(); // Ensure normalization worked // This should not be possible but I will leave it here for now just in case // something was missed #[cfg(debug_assertions)] { if !slerped_normalized.is_normalized() || slerped_normalized.map(f32::is_nan).reduce_or() { panic!( "Failed to normalize {:?} produced from:\nslerp(\n {:?},\n {:?},\n \ {:?},\n)\nWith result: {:?})", slerped, from, to, factor, slerped_normalized ); } } slerped_normalized } /// Begone ye NaN's /// Slerp two `Vec3`s skipping the slerp if their directions are very close /// This avoids a case where `vek`s slerp produces NaN's /// Additionally, it avoids unnecessary calculations if they are near identical /// Assumes `from` is normalized and returns a normalized vector, but `to` /// doesn't need to be normalized /// Returns `Err(from)`` if `to` is unnormalizable // TODO: in some cases we might want to base the slerp rate on the magnitude of // `to` for example when `to` is velocity and `from` is orientation fn slerp_to_unnormalized( from: Vec3, to: Vec3, factor: f32, ) -> Result, Vec3> { to.try_normalized() .map(|to| slerp_normalized(from, to, factor)) .ok_or(from) }