Repository: bitshifter/glam-rs
Pull request #121 (2021-01-17 21:33:48)

Methods to clamp vector length (but with macros)
Reimplement #101 via macros as per [this comment](https://github.com/bitshifter/glam-rs/pull/101#issuecomment-761535689).

@bitshifter How's this look?


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24d92b8..6b53566 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,11 @@ The format is based on [Keep a Changelog], and this project adheres to
 
 ## [Unreleased]
 
+### Added
+
+* Added `clamp_length()`, `clamp_length_max()`, and `clamp_length_min` methods for `Vec2`, `Vec3`,
+  and `Vec4` for `f32` and `f64`.
+
 ## [0.12.0] - 2021-01-15
 
 ### Added
diff --git a/src/vec.rs b/src/vec.rs
index f920c13..b2a51c7 100644
--- a/src/vec.rs
+++ b/src/vec.rs
@@ -320,6 +320,39 @@ macro_rules! impl_vecn_float_methods {
         pub fn abs_diff_eq(self, other: Self, max_abs_diff: $t) -> bool {
             $flttrait::abs_diff_eq(self.0, other.0, max_abs_diff)
         }
+
+        /// Returns a vector with a length no less than `min` and no more than `max`
+        #[inline]
+        pub fn clamp_length(self, min: $t, max: $t) -> Self {
+            let length_sq = self.length_squared();
+            if length_sq < min * min {
+                self * (length_sq.sqrt().recip() * min)
+            } else if length_sq > max * max {
+                self * (length_sq.sqrt().recip() * max)
+            } else {
+                self
+            }
+        }
+
+        /// Returns a vector with a length no more than `max`
+        pub fn clamp_length_max(self, max: $t) -> Self {
+            let length_sq = self.length_squared();
+            if length_sq > max * max {
+                self * (length_sq.sqrt().recip() * max)
+            } else {
+                self
+            }
+        }
+
+        /// Returns a vector with a length no less than `min`
+        pub fn clamp_length_min(self, min: $t) -> Self {
+            let length_sq = self.length_squared();
+            if self.length_squared() < min * min {
+                self * (length_sq.sqrt().recip() * min)
+            } else {
+                self
+            }
+        }
     };
 }
 


Test patch:
diff --git a/tests/vec2.rs b/tests/vec2.rs
index 2a17550..7819e63 100644
--- a/tests/vec2.rs
+++ b/tests/vec2.rs
@@ -563,6 +563,53 @@ macro_rules! impl_vec2_float_tests {
             let angle = $vec2::new(-1.0, 0.0).angle_between($vec2::new(0.0, 1.0));
             assert_approx_eq!(-core::$t::consts::FRAC_PI_2, angle, 1e-6);
         }
+
+        #[test]
+        fn test_clamp_length() {
+            // Too long gets shortened
+            assert_eq!(
+                $vec2::new(12.0, 16.0).clamp_length(7.0, 10.0),
+                $vec2::new(6.0, 8.0) // shortened to length 10.0
+            );
+            // In the middle is unchanged
+            assert_eq!(
+                $vec2::new(2.0, 1.0).clamp_length(0.5, 5.0),
+                $vec2::new(2.0, 1.0) // unchanged
+            );
+            // Too short gets lengthened
+            assert_eq!(
+                $vec2::new(0.6, 0.8).clamp_length(10.0, 20.0),
+                $vec2::new(6.0, 8.0) // lengthened to length 10.0
+            );
+        }
+
+        #[test]
+        fn test_clamp_length_max() {
+            // Too long gets shortened
+            assert_eq!(
+                $vec2::new(12.0, 16.0).clamp_length_max(10.0),
+                $vec2::new(6.0, 8.0) // shortened to length 10.0
+            );
+            // Not too long is unchanged
+            assert_eq!(
+                $vec2::new(2.0, 1.0).clamp_length_max(5.0),
+                $vec2::new(2.0, 1.0) // unchanged
+            );
+        }
+
+        #[test]
+        fn test_clamp_length_min() {
+            // Not too short is unchanged
+            assert_eq!(
+                $vec2::new(2.0, 1.0).clamp_length_min(0.5),
+                $vec2::new(2.0, 1.0) // unchanged
+            );
+            // Too short gets lengthened
+            assert_eq!(
+                $vec2::new(0.6, 0.8).clamp_length_min(10.0),
+                $vec2::new(6.0, 8.0) // lengthened to length 10.0
+            );
+        }
     };
 }
 
diff --git a/tests/vec3.rs b/tests/vec3.rs
index 55a2f53..6005631 100644
--- a/tests/vec3.rs
+++ b/tests/vec3.rs
@@ -612,6 +612,53 @@ macro_rules! impl_vec3_float_tests {
             let angle = $vec3::new(-1.0, 0.0, -1.0).angle_between($vec3::new(1.0, -1.0, 0.0));
             assert_approx_eq!(2.0 * core::$t::consts::FRAC_PI_3, angle, 1e-6);
         }
+
+        #[test]
+        fn test_clamp_length() {
+            // Too long gets shortened
+            assert_eq!(
+                $vec3::new(12.0, 16.0, 0.0).clamp_length(7.0, 10.0),
+                $vec3::new(6.0, 8.0, 0.0) // shortened to length 10.0
+            );
+            // In the middle is unchanged
+            assert_eq!(
+                $vec3::new(2.0, 1.0, 0.0).clamp_length(0.5, 5.0),
+                $vec3::new(2.0, 1.0, 0.0) // unchanged
+            );
+            // Too short gets lengthened
+            assert_eq!(
+                $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
+            );
+        }
+
+        #[test]
+        fn test_clamp_length_max() {
+            // Too long gets shortened
+            assert_eq!(
+                $vec3::new(12.0, 16.0, 0.0).clamp_length_max(10.0),
+                $vec3::new(6.0, 8.0, 0.0) // shortened to length 10.0
+            );
+            // Not too long is unchanged
+            assert_eq!(
+                $vec3::new(2.0, 1.0, 0.0).clamp_length_max(5.0),
+                $vec3::new(2.0, 1.0, 0.0) // unchanged
+            );
+        }
+
+        #[test]
+        fn test_clamp_length_min() {
+            // Not too short is unchanged
+            assert_eq!(
+                $vec3::new(2.0, 1.0, 0.0).clamp_length_min(0.5),
+                $vec3::new(2.0, 1.0, 0.0) // unchanged
+            );
+            // Too short gets lengthened
+            assert_eq!(
+                $vec3::new(0.6, 0.8, 0.0).clamp_length_min(10.0),
+                $vec3::new(6.0, 8.0, 0.0) // lengthened to length 10.0
+            );
+        }
     };
 }
 
diff --git a/tests/vec4.rs b/tests/vec4.rs
index b38654a..20315b8 100644
--- a/tests/vec4.rs
+++ b/tests/vec4.rs
@@ -669,6 +669,53 @@ macro_rules! impl_vec4_float_tests {
                 )
             );
         }
+
+        #[test]
+        fn test_clamp_length() {
+            // Too long gets shortened
+            assert_eq!(
+                $vec4::new(12.0, 16.0, 0.0, 0.0).clamp_length(7.0, 10.0),
+                $vec4::new(6.0, 8.0, 0.0, 0.0) // shortened to length 10.0
+            );
+            // In the middle is unchanged
+            assert_eq!(
+                $vec4::new(2.0, 1.0, 0.0, 0.0).clamp_length(0.5, 5.0),
+                $vec4::new(2.0, 1.0, 0.0, 0.0) // unchanged
+            );
+            // Too short gets lengthened
+            assert_eq!(
+                $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
+            );
+        }
+
+        #[test]
+        fn test_clamp_length_max() {
+            // Too long gets shortened
+            assert_eq!(
+                $vec4::new(12.0, 16.0, 0.0, 0.0).clamp_length_max(10.0),
+                $vec4::new(6.0, 8.0, 0.0, 0.0) // shortened to length 10.0
+            );
+            // Not too long is unchanged
+            assert_eq!(
+                $vec4::new(2.0, 1.0, 0.0, 0.0).clamp_length_max(5.0),
+                $vec4::new(2.0, 1.0, 0.0, 0.0) // unchanged
+            );
+        }
+
+        #[test]
+        fn test_clamp_length_min() {
+            // Not too short is unchanged
+            assert_eq!(
+                $vec4::new(2.0, 1.0, 0.0, 0.0).clamp_length_min(0.5),
+                $vec4::new(2.0, 1.0, 0.0, 0.0) // unchanged
+            );
+            // Too short gets lengthened
+            assert_eq!(
+                $vec4::new(0.6, 0.8, 0.0, 0.0).clamp_length_min(10.0),
+                $vec4::new(6.0, 8.0, 0.0, 0.0) // lengthened to length 10.0
+            );
+        }
     };
 }