Repository: bitshifter/glam-rs
Pull request #128 (2021-02-10 15:04:35)

Add try_normalize for all real vectors
This is a follow-up to https://github.com/bitshifter/glam-rs/pull/127 and https://github.com/bitshifter/glam-rs/issues/74

`fn try_normalize(&self) -> Option<Self>` is a natural choice when you either:

* Want to early-out on invalid vectors: `v.try_normalize()?`
* Want to do proper error handling: `v.try_normalize.ok_or(BadInputError)?`
* Do hard panics on invalid input: `v.try_normalize().unwrap()`


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2370be2..21c98ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog], and this project adheres to
 * Added `ZERO` constant for vectors and matrices.
 * Added `clamp_length()`, `clamp_length_max()`, and `clamp_length_min` methods for `Vec2`, `Vec3`,
   and `Vec4` for `f32` and `f64`.
+* Added `try_normalize()` and `normalize_or_zero()` for all real vector types.
 
 ### Changed
 * Deprecated `::unit_x/y/z()`, `::zero()`, `::one()`, `::identity()` functions in favor of constants.
diff --git a/src/vec.rs b/src/vec.rs
index 9ed546f..96c6ed9 100644
--- a/src/vec.rs
+++ b/src/vec.rs
@@ -247,17 +247,36 @@ macro_rules! impl_vecn_float_methods {
         /// Returns `self` normalized to length 1.0.
         ///
         /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
-        /// You can check the results with [`Self::is_finite`], or use [`Self::normalize_or_zero`].
+        ///
+        /// See also [`Self::try_normalize`] and [`Self::normalize_or_zero`].
         #[inline(always)]
         pub fn normalize(self) -> Self {
             Self($flttrait::normalize(self.0))
         }
 
+        /// Returns `self` normalized to length 1.0 if possible, else returns `None`.
+        ///
+        /// In particular, if the input is zero (or very close to zero), or non-finite,
+        /// the result of this operation will be `None`.
+        ///
+        /// See also [`Self::normalize_or_zero`].
+        #[inline]
+        pub fn try_normalize(self) -> Option<Self> {
+            let rcp = self.length_recip();
+            if rcp.is_finite() && rcp > 0.0 {
+                Some(self * rcp)
+            } else {
+                None
+            }
+        }
+
         /// Returns `self` normalized to length 1.0 if possible, else returns zero.
         ///
         /// In particular, if the input is zero (or very close to zero), or non-finite,
         /// the result of this operation will be zero.
-        #[inline(always)]
+        ///
+        /// See also [`Self::try_normalize`].
+        #[inline]
         pub fn normalize_or_zero(self) -> Self {
             let rcp = self.length_recip();
             if rcp.is_finite() && rcp > 0.0 {


Test patch:
diff --git a/tests/support/macros.rs b/tests/support/macros.rs
index 4f62657..dffe537 100644
--- a/tests/support/macros.rs
+++ b/tests/support/macros.rs
@@ -61,9 +61,36 @@ macro_rules! impl_vec_float_normalize_tests {
             assert!(!from_x_y(NAN, 0.0).normalize().is_finite());
         }
 
+        #[test]
+        fn test_try_normalize() {
+            assert_eq!(
+                from_x_y(-42.0, 0.0).try_normalize(),
+                Some(from_x_y(-1.0, 0.0))
+            );
+            assert_eq!(
+                from_x_y(MAX.sqrt(), 0.0).try_normalize(),
+                Some(from_x_y(1.0, 0.0))
+            );
+
+            // We expect `try_normalize` to return None when inputs are very small:
+            assert_eq!(from_x_y(0.0, 0.0).try_normalize(), None);
+            assert_eq!(from_x_y(MIN_POSITIVE, 0.0).try_normalize(), None);
+
+            // We expect `try_normalize` to return None when inputs are non-finite:
+            assert_eq!(from_x_y(INFINITY, 0.0).try_normalize(), None);
+            assert_eq!(from_x_y(NAN, 0.0).try_normalize(), None);
+
+            // We expect `try_normalize` to return None when inputs are very large:
+            assert_eq!(from_x_y(MAX, 0.0).try_normalize(), None);
+            assert_eq!(from_x_y(MAX, MAX).try_normalize(), None);
+        }
+
         #[test]
         fn test_normalize_or_zero() {
-            assert_eq!(from_x_y(-42.0, 0.0).normalize(), from_x_y(-1.0, 0.0));
+            assert_eq!(
+                from_x_y(-42.0, 0.0).normalize_or_zero(),
+                from_x_y(-1.0, 0.0)
+            );
             assert_eq!(
                 from_x_y(MAX.sqrt(), 0.0).normalize_or_zero(),
                 from_x_y(1.0, 0.0)