Repository: bitshifter/glam-rs (rust)

Issue:
Add method to vector types that returns direction and length
It's not uncommon to find the direction and length of a vector, but it is a hassle to do it manually. It would be good to have a method for this.

Pull request:
Add vector normalize_and_length method.
Returns the normal and length of self.

# Objective

- Add vector method that returns the normal and length of self.
- Fixes #291 


Patch:
diff --git a/src/f32/coresimd/vec3a.rs b/src/f32/coresimd/vec3a.rs
index 2b98cfa..2d113a7 100644
--- a/src/f32/coresimd/vec3a.rs
+++ b/src/f32/coresimd/vec3a.rs
@@ -626,6 +626,21 @@ impl Vec3A {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f32) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f32/coresimd/vec4.rs b/src/f32/coresimd/vec4.rs
index 0a4e94d..4560238 100644
--- a/src/f32/coresimd/vec4.rs
+++ b/src/f32/coresimd/vec4.rs
@@ -620,6 +620,21 @@ impl Vec4 {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f32) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f32/neon/vec3a.rs b/src/f32/neon/vec3a.rs
index 6e1a517..131b3e5 100644
--- a/src/f32/neon/vec3a.rs
+++ b/src/f32/neon/vec3a.rs
@@ -668,6 +668,21 @@ impl Vec3A {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f32) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f32/neon/vec4.rs b/src/f32/neon/vec4.rs
index 03bbc9e..d5c9abf 100644
--- a/src/f32/neon/vec4.rs
+++ b/src/f32/neon/vec4.rs
@@ -649,6 +649,21 @@ impl Vec4 {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f32) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f32/scalar/vec3a.rs b/src/f32/scalar/vec3a.rs
index 6b17f45..cdc1880 100644
--- a/src/f32/scalar/vec3a.rs
+++ b/src/f32/scalar/vec3a.rs
@@ -649,6 +649,21 @@ impl Vec3A {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f32) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f32/scalar/vec4.rs b/src/f32/scalar/vec4.rs
index 16cd364..03a60c7 100644
--- a/src/f32/scalar/vec4.rs
+++ b/src/f32/scalar/vec4.rs
@@ -703,6 +703,21 @@ impl Vec4 {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f32) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f32/sse2/vec3a.rs b/src/f32/sse2/vec3a.rs
index 27b324a..9e6c489 100644
--- a/src/f32/sse2/vec3a.rs
+++ b/src/f32/sse2/vec3a.rs
@@ -667,6 +667,21 @@ impl Vec3A {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f32) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f32/sse2/vec4.rs b/src/f32/sse2/vec4.rs
index 5fd101a..66c6aa0 100644
--- a/src/f32/sse2/vec4.rs
+++ b/src/f32/sse2/vec4.rs
@@ -657,6 +657,21 @@ impl Vec4 {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f32) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f32/vec2.rs b/src/f32/vec2.rs
index d3b780b..d80f591 100644
--- a/src/f32/vec2.rs
+++ b/src/f32/vec2.rs
@@ -572,6 +572,21 @@ impl Vec2 {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f32) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f32/vec3.rs b/src/f32/vec3.rs
index 557dd68..c71b17c 100644
--- a/src/f32/vec3.rs
+++ b/src/f32/vec3.rs
@@ -639,6 +639,21 @@ impl Vec3 {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f32) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f32/wasm32/vec3a.rs b/src/f32/wasm32/vec3a.rs
index 404586d..3589fc5 100644
--- a/src/f32/wasm32/vec3a.rs
+++ b/src/f32/wasm32/vec3a.rs
@@ -635,6 +635,21 @@ impl Vec3A {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f32) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f32/wasm32/vec4.rs b/src/f32/wasm32/vec4.rs
index 7d3c50a..e4b5670 100644
--- a/src/f32/wasm32/vec4.rs
+++ b/src/f32/wasm32/vec4.rs
@@ -629,6 +629,21 @@ impl Vec4 {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f32) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f64/dvec2.rs b/src/f64/dvec2.rs
index 07d24d6..cf99d6d 100644
--- a/src/f64/dvec2.rs
+++ b/src/f64/dvec2.rs
@@ -572,6 +572,21 @@ impl DVec2 {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f64) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f64/dvec3.rs b/src/f64/dvec3.rs
index 1105165..4998194 100644
--- a/src/f64/dvec3.rs
+++ b/src/f64/dvec3.rs
@@ -639,6 +639,21 @@ impl DVec3 {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f64) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/src/f64/dvec4.rs b/src/f64/dvec4.rs
index b5227ba..75d537d 100644
--- a/src/f64/dvec4.rs
+++ b/src/f64/dvec4.rs
@@ -692,6 +692,21 @@ impl DVec4 {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, f64) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.
diff --git a/templates/vec.rs.tera b/templates/vec.rs.tera
index 2829a8f..d73cf0b 100644
--- a/templates/vec.rs.tera
+++ b/templates/vec.rs.tera
@@ -1795,6 +1795,21 @@ impl {{ self_t }} {
         self.normalize_or(Self::ZERO)
     }
 
+    /// Returns `self` normalized to length 1.0 and the length of `self`.
+    ///
+    /// If `self` is zero length then `(Self::X, 0.0)` is returned.
+    #[inline]
+    #[must_use]
+    pub fn normalize_and_length(self) -> (Self, {{ scalar_t }}) {
+        let length = self.length();
+        let rcp = 1.0 / length;
+        if rcp.is_finite() && rcp > 0.0 {
+            (self * rcp, length)
+        } else {
+            (Self::X, 0.0)
+        }
+    }
+
     /// Returns whether `self` is length `1.0` or not.
     ///
     /// Uses a precision threshold of approximately `1e-4`.


Test patch:
diff --git a/tests/support/macros.rs b/tests/support/macros.rs
index 4babd87..fded9b3 100644
--- a/tests/support/macros.rs
+++ b/tests/support/macros.rs
@@ -188,6 +188,41 @@ macro_rules! impl_vec_float_normalize_tests {
             assert_eq!(from_x_y($t::MAX, 0.0).normalize_or_zero(), $vec::ZERO);
             assert_eq!(from_x_y($t::MAX, $t::MAX).normalize_or_zero(), $vec::ZERO);
         });
+
+        glam_test!(test_normalize_and_length, {
+            assert_eq!(
+                from_x_y(-42.0, 0.0).normalize_and_length(),
+                (from_x_y(-1.0, 0.0), 42.0)
+            );
+            assert_eq!(
+                from_x_y($t::MAX.sqrt(), 0.0).normalize_and_length(),
+                (from_x_y(1.0, 0.0), $t::MAX.sqrt())
+            );
+
+            // We expect `normalize_and_length` to return zero length when inputs are very small:
+            assert_eq!(from_x_y(0.0, 0.0).normalize_and_length(), ($vec::X, 0.0));
+            assert_eq!(
+                from_x_y($t::MIN_POSITIVE, 0.0).normalize_and_length(),
+                ($vec::X, 0.0)
+            );
+
+            // We expect `normalize_and_length` to return zero length when inputs are non-finite:
+            assert_eq!(
+                from_x_y($t::INFINITY, 0.0).normalize_and_length(),
+                ($vec::X, 0.0)
+            );
+            assert_eq!(
+                from_x_y($t::NAN, 0.0).normalize_and_length(),
+                ($vec::X, 0.0)
+            );
+
+            // We expect `normalize_and_length` to return zero length when inputs are very large:
+            assert_eq!(
+                from_x_y($t::MAX, 0.0).normalize_and_length(),
+                ($vec::X, 0.0)
+            );
+            assert_eq!(from_x_y($t::MAX, $t::MAX).normalize_or_zero(), $vec::ZERO);
+        });
     };
 }