Repository: bitshifter/glam-rs
Pull request #202 (2021-06-21 10:12:07)

Added vector projection and rejection methods with tests.


Patch:
diff --git a/src/core/sse2/vector.rs b/src/core/sse2/vector.rs
index 2f6f467..f31a0f6 100644
--- a/src/core/sse2/vector.rs
+++ b/src/core/sse2/vector.rs
@@ -690,6 +690,7 @@ impl FloatVector3<f32> for __m128 {
     fn normalize(self) -> Self {
         unsafe {
             let length = _mm_sqrt_ps(Vector3::dot_into_vec(self, self));
+            #[allow(clippy::let_and_return)]
             let normalized = _mm_div_ps(self, length);
             glam_assert!(FloatVector3::is_finite(normalized));
             normalized
@@ -782,6 +783,7 @@ impl FloatVector4<f32> for __m128 {
     fn normalize(self) -> Self {
         unsafe {
             let dot = Vector4::dot_into_vec(self, self);
+            #[allow(clippy::let_and_return)]
             let normalized = _mm_div_ps(self, _mm_sqrt_ps(dot));
             glam_assert!(FloatVector4::is_finite(normalized));
             normalized
diff --git a/src/core/traits/vector.rs b/src/core/traits/vector.rs
index a782dfe..468413d 100644
--- a/src/core/traits/vector.rs
+++ b/src/core/traits/vector.rs
@@ -549,6 +549,7 @@ pub trait FloatVector2<T: FloatEx>: SignedVector2<T> {
 
     #[inline]
     fn normalize(self) -> Self {
+        #[allow(clippy::let_and_return)]
         let normalized = self.mul_scalar(self.length_recip());
         glam_assert!(normalized.is_finite());
         normalized
@@ -647,6 +648,7 @@ pub trait FloatVector3<T: FloatEx>: SignedVector3<T> {
 
     #[inline]
     fn normalize(self) -> Self {
+        #[allow(clippy::let_and_return)]
         let normalized = self.mul_scalar(self.length_recip());
         glam_assert!(normalized.is_finite());
         normalized
@@ -774,6 +776,7 @@ pub trait FloatVector4<T: FloatEx>: SignedVector4<T> {
 
     #[inline]
     fn normalize(self) -> Self {
+        #[allow(clippy::let_and_return)]
         let normalized = self.mul_scalar(self.length_recip());
         glam_assert!(normalized.is_finite());
         normalized
diff --git a/src/vec.rs b/src/vec.rs
index 9e2d765..07b2add 100644
--- a/src/vec.rs
+++ b/src/vec.rs
@@ -313,6 +313,67 @@ macro_rules! impl_vecn_float_methods {
             $flttrait::is_normalized(self.0)
         }
 
+        /// Returns the vector projection of `self` onto `other`.
+        ///
+        /// `other` must be of non-zero length.
+        ///
+        /// # Panics
+        ///
+        /// Will panic if `other` is zero length when `glam_assert` is enabled.
+        #[must_use]
+        #[inline]
+        pub fn project_onto(self, other: Self) -> Self {
+            let other_len_sq_rcp = other.dot(other).recip();
+            glam_assert!(other_len_sq_rcp.is_finite());
+            other * self.dot(other) * other_len_sq_rcp
+        }
+
+        /// Returns the vector rejection of `self` from `other`.
+        ///
+        /// The vector rejection is the vector perpendicular to the projection of `self` onto
+        /// `other`, in other words the result of `self - self.project_onto(other)`.
+        ///
+        /// `other` must be of non-zero length.
+        ///
+        /// # Panics
+        ///
+        /// Will panic if `other` has a length of zero when `glam_assert` is enabled.
+        #[must_use]
+        #[inline]
+        pub fn reject_from(self, other: Self) -> Self {
+            self - self.project_onto(other)
+        }
+
+        /// Returns the vector projection of `self` onto `other`.
+        ///
+        /// `other` must be normalized.
+        ///
+        /// # Panics
+        ///
+        /// Will panic if `other` is not normalized when `glam_assert` is enabled.
+        #[must_use]
+        #[inline]
+        pub fn project_onto_normalized(self, other: Self) -> Self {
+            glam_assert!(other.is_normalized());
+            other * self.dot(other)
+        }
+
+        /// Returns the vector rejection of `self` from `other`.
+        ///
+        /// The vector rejection is the vector perpendicular to the projection of `self` onto
+        /// `other`, in other words the result of `self - self.project_onto(other)`.
+        ///
+        /// `other` must be normalized.
+        ///
+        /// # Panics
+        ///
+        /// Will panic if `other` is not normalized when `glam_assert` is enabled.
+        #[must_use]
+        #[inline]
+        pub fn reject_from_normalized(self, other: Self) -> Self {
+            self - self.project_onto_normalized(other)
+        }
+
         /// Returns a vector containing the nearest integer to a number for each element of `self`.
         /// Round half-way cases away from 0.0.
         #[inline(always)]
diff --git a/src/vec3.rs b/src/vec3.rs
index 4f77e68..c335f12 100644
--- a/src/vec3.rs
+++ b/src/vec3.rs
@@ -217,8 +217,8 @@ macro_rules! impl_vec3_float_methods {
             Self::new(b, sign + self.y * self.y * a, -self.y)
         }
 
-        /// Given a unit-length vector return two other vectors that together form an orthonormal basis.
-        /// That is, all three vectors are orthogonal to each other and are normalized.
+        /// Given a unit-length vector return two other vectors that together form an orthonormal
+        /// basis.  That is, all three vectors are orthogonal to each other and are normalized.
         ///
         /// # Panics
         ///


Test patch:
diff --git a/tests/vec2.rs b/tests/vec2.rs
index 2c85708..6987d8f 100644
--- a/tests/vec2.rs
+++ b/tests/vec2.rs
@@ -499,6 +499,23 @@ macro_rules! impl_vec2_float_tests {
             assert_eq!($new(0.5, 0.25), $new(2.0, 4.0).recip());
         }
 
+        #[test]
+        fn test_project_reject() {
+            assert_eq!($new(0.0, 1.0), $new(1.0, 1.0).project_onto($new(0.0, 2.0)));
+            assert_eq!($new(1.0, 0.0), $new(1.0, 1.0).reject_from($new(0.0, 2.0)));
+            assert_eq!(
+                $new(0.0, 1.0),
+                $new(1.0, 1.0).project_onto_normalized($new(0.0, 1.0))
+            );
+            assert_eq!(
+                $new(1.0, 0.0),
+                $new(1.0, 1.0).reject_from_normalized($new(0.0, 1.0))
+            );
+            should_glam_assert!({ $vec2::ONE.project_onto($vec2::ZERO) });
+            should_glam_assert!({ $vec2::ONE.reject_from($vec2::ZERO) });
+            should_glam_assert!({ $vec2::ONE.project_onto_normalized($vec2::ONE) });
+            should_glam_assert!({ $vec2::ONE.reject_from_normalized($vec2::ONE) });
+        }
         #[test]
         fn test_perp() {
             let v1 = $vec2::new(1.0, 2.0);
@@ -647,7 +664,7 @@ macro_rules! impl_vec2_float_tests {
                 $vec2::new(0.6, 0.8).clamp_length(10.0, 20.0),
                 $vec2::new(6.0, 8.0) // lengthened to length 10.0
             );
-            should_glam_assert!({ $vec2::ONE.clamp_length($vec2::ONE, $vec2::ZERO) });
+            should_glam_assert!({ $vec2::ONE.clamp_length(1.0, 0.0) });
         }
 
         #[test]
diff --git a/tests/vec3.rs b/tests/vec3.rs
index bfdf297..795b3f0 100644
--- a/tests/vec3.rs
+++ b/tests/vec3.rs
@@ -556,6 +556,30 @@ macro_rules! impl_vec3_float_tests {
             assert_eq!($new(0.5, 0.25, 0.125), $new(2.0, 4.0, 8.0).recip());
         }
 
+        #[test]
+        fn test_project_reject() {
+            assert_eq!(
+                $new(0.0, 0.0, 1.0),
+                $new(1.0, 0.0, 1.0).project_onto($new(0.0, 0.0, 2.0))
+            );
+            assert_eq!(
+                $new(1.0, 0.0, 0.0),
+                $new(1.0, 0.0, 1.0).reject_from($new(0.0, 0.0, 2.0))
+            );
+            assert_eq!(
+                $new(0.0, 0.0, 1.0),
+                $new(1.0, 0.0, 1.0).project_onto_normalized($new(0.0, 0.0, 1.0))
+            );
+            assert_eq!(
+                $new(1.0, 0.0, 0.0),
+                $new(1.0, 0.0, 1.0).reject_from_normalized($new(0.0, 0.0, 1.0))
+            );
+            should_glam_assert!({ $vec3::ONE.project_onto($vec3::ZERO) });
+            should_glam_assert!({ $vec3::ONE.reject_from($vec3::ZERO) });
+            should_glam_assert!({ $vec3::ONE.project_onto_normalized($vec3::ONE) });
+            should_glam_assert!({ $vec3::ONE.reject_from_normalized($vec3::ONE) });
+        }
+
         #[test]
         fn test_signum() {
             assert_eq!($vec3::ZERO.signum(), $vec3::ONE);
@@ -699,7 +723,7 @@ macro_rules! impl_vec3_float_tests {
                 $vec3::new(0.6, 0.8, 0.0).clamp_length(10.0, 20.0),
                 $vec3::new(6.0, 8.0, 0.0) // lengthened to length 10.0
             );
-            should_glam_assert!({ $vec3::ONE.clamp_length($vec3::ONE, $vec3::ZERO) });
+            should_glam_assert!({ $vec3::ONE.clamp_length(1.0, 0.0) });
         }
 
         #[test]
@@ -730,8 +754,8 @@ macro_rules! impl_vec3_float_tests {
             );
         }
 
-        #[test]
         #[cfg(feature = "std")]
+        #[test]
         fn test_any_ortho() {
             let eps = 2.0 * core::$t::EPSILON;
 
diff --git a/tests/vec4.rs b/tests/vec4.rs
index 417ae84..a2ca5c6 100644
--- a/tests/vec4.rs
+++ b/tests/vec4.rs
@@ -622,6 +622,30 @@ macro_rules! impl_vec4_float_tests {
             );
         }
 
+        #[test]
+        fn test_project_reject() {
+            assert_eq!(
+                $new(0.0, 0.0, 0.0, 1.0),
+                $new(0.0, 1.0, 0.0, 1.0).project_onto($new(0.0, 0.0, 0.0, 2.0))
+            );
+            assert_eq!(
+                $new(0.0, 1.0, 0.0, 0.0),
+                $new(0.0, 1.0, 0.0, 1.0).reject_from($new(0.0, 0.0, 0.0, 2.0))
+            );
+            assert_eq!(
+                $new(0.0, 0.0, 0.0, 1.0),
+                $new(0.0, 1.0, 0.0, 1.0).project_onto_normalized($new(0.0, 0.0, 0.0, 1.0))
+            );
+            assert_eq!(
+                $new(0.0, 1.0, 0.0, 0.0),
+                $new(0.0, 1.0, 0.0, 1.0).reject_from_normalized($new(0.0, 0.0, 0.0, 1.0))
+            );
+            should_glam_assert!({ $vec4::ONE.project_onto($vec4::ZERO) });
+            should_glam_assert!({ $vec4::ONE.reject_from($vec4::ZERO) });
+            should_glam_assert!({ $vec4::ONE.project_onto_normalized($vec4::ONE) });
+            should_glam_assert!({ $vec4::ONE.reject_from_normalized($vec4::ONE) });
+        }
+
         #[test]
         fn test_signum() {
             assert_eq!($vec4::ZERO.signum(), $vec4::ONE);
@@ -760,7 +784,7 @@ macro_rules! impl_vec4_float_tests {
                 $vec4::new(0.6, 0.8, 0.0, 0.0).clamp_length(10.0, 20.0),
                 $vec4::new(6.0, 8.0, 0.0, 0.0) // lengthened to length 10.0
             );
-            should_glam_assert!({ $vec4::ONE.clamp_length($vec4::ONE, $vec4::ZERO) });
+            should_glam_assert!({ $vec4::ONE.clamp_length(1.0, 0.0) });
         }
 
         #[test]