diff --git a/common/src/comp/ori.rs b/common/src/comp/ori.rs index e0fd9bf49d..74aff18e30 100644 --- a/common/src/comp/ori.rs +++ b/common/src/comp/ori.rs @@ -1,4 +1,4 @@ -use crate::util::Dir; +use crate::util::{Dir, Plane, Projection}; use serde::{Deserialize, Serialize}; use specs::Component; use specs_idvs::IdvStorage; @@ -24,21 +24,18 @@ impl Ori { } /// Tries to convert into a Dir and then the appropriate rotation - pub fn from_unnormalized_vec(vec: Vec3) -> Option { - Dir::from_unnormalized(vec).map(Self::from) + pub fn from_unnormalized_vec(vec: T) -> Option + where + T: Into>, + { + Dir::from_unnormalized(vec.into()).map(Self::from) } pub fn to_vec(self) -> Vec3 { *self.look_dir() } pub fn to_quat(self) -> Quaternion { self.0 } - /// Transform the vector from local into global vector space - pub fn relative_to_world(&self, vec: Vec3) -> Vec3 { self.0 * vec } - - /// Transform the vector from global into local vector space - pub fn relative_to_self(&self, vec: Vec3) -> Vec3 { self.0.inverse() * vec } - - pub fn look_dir(&self) -> Dir { Dir::new(self.0.normalized() * *Dir::default()) } + pub fn look_dir(&self) -> Dir { Dir::new(self.0 * *Dir::default()) } pub fn up(&self) -> Dir { self.pitched_up(PI / 2.0).look_dir() } @@ -55,10 +52,28 @@ impl Ori { pub fn slerped_towards(self, ori: Ori, s: f32) -> Self { Self::slerp(self, ori, s) } /// Multiply rotation quaternion by `q` + /// (the rotations are in local vector space). pub fn rotated(self, q: Quaternion) -> Self { Self((self.0 * q).normalized()) } /// Premultiply rotation quaternion by `q` - pub fn rotated_world(self, q: Quaternion) -> Self { Self((q * self.0).normalized()) } + /// (the rotations are in global vector space). + pub fn prerotated(self, q: Quaternion) -> Self { Self((q * self.0).normalized()) } + + /// Take `global` into this Ori's local vector space + pub fn global_to_local(&self, global: T) -> as std::ops::Mul>::Output + where + Quaternion: std::ops::Mul, + { + self.0.inverse() * global + } + + /// Take `local` into the global vector space + pub fn local_to_global(&self, local: T) -> as std::ops::Mul>::Output + where + Quaternion: std::ops::Mul, + { + self.0 * local + } pub fn pitched_up(self, angle_radians: f32) -> Self { self.rotated(Quaternion::rotation_x(angle_radians)) @@ -85,24 +100,46 @@ impl Ori { } /// Returns a version without sideways tilt (roll) - pub fn uprighted(self) -> Self { self.look_dir().into() } + /// + /// # Examples + /// ``` + /// use veloren_common::comp::Ori; + /// + /// let ang = 45_f32.to_radians(); + /// let zenith = vek::Vec3::unit_z(); + /// + /// let rl = Ori::default().rolled_left(ang); + /// assert!((rl.up().angle_between(zenith) - ang).abs() < std::f32::EPSILON); + /// assert!(rl.uprighted().up().angle_between(zenith) < std::f32::EPSILON); + /// + /// let pd_rr = Ori::default().pitched_down(ang).rolled_right(ang); + /// let pd_upr = pd_rr.uprighted(); + /// + /// assert!((pd_upr.up().angle_between(zenith) - ang).abs() < std::f32::EPSILON); + /// + /// let ang1 = pd_upr.rolled_right(ang).up().angle_between(zenith); + /// let ang2 = pd_rr.up().angle_between(zenith); + /// assert!((ang1 - ang2).abs() < std::f32::EPSILON); + /// ``` + pub fn uprighted(self) -> Self { + let fw = self.look_dir(); + match Dir::new(Vec3::unit_z()).projected(&Plane::from(fw)) { + Some(dir_p) => { + let up = self.up(); + let go_right_s = fw.cross(*up).dot(*dir_p).signum(); + self.rolled_right(up.angle_between(*dir_p) * go_right_s) + }, + None => self, + } + } fn is_normalized(&self) -> bool { self.0.into_vec4().is_normalized() } } impl From for Ori { fn from(dir: Dir) -> Self { - // rotate horizontally first and then vertically to prevent rolling let from = *Dir::default(); - let q1 = (*dir * Vec3::new(1.0, 1.0, 0.0)) - .try_normalized() - .map(|hv| Quaternion::::rotation_from_to_3d(from, hv).normalized()) - .unwrap_or_default(); - let q2 = (from + Vec3::new(0.0, 0.0, dir.z)) - .try_normalized() - .map(|to| Quaternion::::rotation_from_to_3d(from, to).normalized()) - .unwrap_or_default(); - Self((q1 * q2).normalized()) + Self::from(Quaternion::::rotation_from_to_3d(from, *dir)).uprighted() } } @@ -118,7 +155,7 @@ impl From> for Ori { fn from( vek::quaternion::repr_simd::Quaternion { x, y, z, w }: vek::quaternion::repr_simd::Quaternion, ) -> Self { - Self(Quaternion { x, y, z, w }.normalized()) + Self::from(Quaternion { x, y, z, w }) } } @@ -136,10 +173,18 @@ impl From for Vec3 { fn from(ori: Ori) -> Self { *ori.look_dir() } } +impl From for vek::vec::repr_simd::Vec3 { + fn from(ori: Ori) -> Self { vek::vec::repr_simd::Vec3::from(*ori.look_dir()) } +} + impl From for Vec2 { fn from(ori: Ori) -> Self { ori.look_dir().xy() } } +impl From for vek::vec::repr_simd::Vec2 { + fn from(ori: Ori) -> Self { vek::vec::repr_simd::Vec2::from(ori.look_dir().xy()) } +} + // Validate at Deserialization #[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] struct SerdeOri(Quaternion); diff --git a/common/src/util/dir.rs b/common/src/util/dir.rs index 1ba5055f4d..9f76484479 100644 --- a/common/src/util/dir.rs +++ b/common/src/util/dir.rs @@ -1,3 +1,4 @@ +use super::{Plane, Projection}; use serde::{Deserialize, Serialize}; use tracing::warn; use vek::*; @@ -95,8 +96,31 @@ impl std::ops::Deref for Dir { fn deref(&self) -> &Vec3 { &self.0 } } -impl From> for Dir { - fn from(dir: Vec3) -> Self { Dir::new(dir) } +impl From for Vec3 { + fn from(dir: Dir) -> Self { *dir } +} + +impl std::ops::Mul for Quaternion { + type Output = Dir; + + fn mul(self, dir: Dir) -> Self::Output { Dir::new(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::Neg for Dir { diff --git a/common/src/util/mod.rs b/common/src/util/mod.rs index 3da0f632a9..8b0af5ac59 100644 --- a/common/src/util/mod.rs +++ b/common/src/util/mod.rs @@ -2,6 +2,8 @@ mod color; pub mod dir; pub mod find_dist; mod option; +pub mod plane; +pub mod projection; pub mod userdata_dir; pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash")); @@ -28,6 +30,8 @@ lazy_static::lazy_static! { pub use color::*; pub use dir::*; pub use option::*; +pub use plane::*; +pub use projection::*; #[cfg(feature = "tracy")] pub use tracy_client; diff --git a/common/src/util/plane.rs b/common/src/util/plane.rs new file mode 100644 index 0000000000..48be63c556 --- /dev/null +++ b/common/src/util/plane.rs @@ -0,0 +1,53 @@ +use super::{Dir, Projection}; +use serde::{Deserialize, Serialize}; +use vek::*; + +/// Plane + +// plane defined by its normal and origin +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Plane { + pub normal: Dir, + /// Distance from origin in the direction of normal + pub d: f32, +} + +impl Plane { + pub fn new(dir: Dir) -> Self { Self::from(dir) } + + pub fn distance(&self, to: Vec3) -> f32 { self.normal.dot(to) - self.d } + + // fn center(&self) -> Vec3 { *self.normal * self.d } + + pub fn projection(&self, v: Vec3) -> Vec3 { v - *self.normal * self.distance(v) } + + pub fn xy() -> Self { Plane::from(Dir::new(Vec3::unit_z())) } + + pub fn yz() -> Self { Plane::from(Dir::new(Vec3::unit_x())) } + + pub fn zx() -> Self { Plane::from(Dir::new(Vec3::unit_y())) } +} + +impl From for Plane { + fn from(dir: Dir) -> Self { + Plane { + normal: dir, + d: 0.0, + } + } +} + +impl Projection for Vec3 { + type Output = Vec3; + + fn projected(self, plane: &Plane) -> Self::Output { plane.projection(self) } +} + +impl Projection for Extent2 +where + T: Projection, +{ + type Output = Self; + + fn projected(self, plane: &Plane) -> Self::Output { self.map(|v| v.projected(plane)) } +} diff --git a/common/src/util/projection.rs b/common/src/util/projection.rs new file mode 100644 index 0000000000..296b6fddc4 --- /dev/null +++ b/common/src/util/projection.rs @@ -0,0 +1,28 @@ +use vek::{Vec2, Vec3}; + +/// Projection trait for projection of linear types and shapes +pub trait Projection { + type Output; + + fn projected(self, onto: &T) -> Self::Output; +} + +// Impls + +impl Projection> for Vec2 { + type Output = Self; + + fn projected(self, v: &Self) -> Self::Output { + let v = *v; + self.dot(v) * v / v.magnitude_squared() + } +} + +impl Projection> for Vec3 { + type Output = Self; + + fn projected(self, v: &Self) -> Self::Output { + let v = *v; + v * self.dot(v) / v.magnitude_squared() + } +}