Repository: bitshifter/glam-rs
Pull request #527 (2024-06-16 11:39:28)

Clarified that vectors must be finite to be normalized.
Added is_finite_mask method which has simd implementations.


Patch:
diff --git a/codegen/templates/vec.rs.tera b/codegen/templates/vec.rs.tera
index 3b9d33f..5c8c080 100644
--- a/codegen/templates/vec.rs.tera
+++ b/codegen/templates/vec.rs.tera
@@ -1334,16 +1334,33 @@ impl {{ self_t }} {
     #[inline]
     #[must_use]
     pub fn is_finite(self) -> bool {
-        {% if is_coresimd %}
-            {% if dim == 3 %}
-                f32x4::is_finite(self.0).bitor(mask32x4::from_array([false, false, false, true])).all()
-            {% elif dim == 4 %}
-                f32x4::is_finite(self.0).all()
-            {% endif %}
-        {% else %}
+        {% if is_scalar  %}
             {% for c in components %}
                 self.{{ c }}.is_finite() {% if not loop.last %} && {% endif %}
             {%- endfor %}
+        {% else %}
+            self.is_finite_mask().all()
+        {% endif %}
+    }
+
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> {{ mask_t }} {
+        {% if is_scalar %}
+            {{ mask_t }}::new(
+                {% for c in components %}
+                    self.{{ c }}.is_finite(),
+                {%- endfor %}
+            )
+        {% elif is_sse2 %}
+            {{ mask_t }}(unsafe { _mm_cmplt_ps(crate::sse2::m128_abs(self.0), Self::INFINITY.0) })
+        {% elif is_wasm32 %}
+            {{ mask_t }}(f32x4_lt(f32x4_abs(self.0), Self::INFINITY.0))
+        {% elif is_coresimd %}
+            {{ mask_t }}(f32x4::is_finite(self.0))
+        {% elif is_neon %}
+            {{ mask_t }}(unsafe { vcltq_f32(vabsq_f32(self.0), Self::INFINITY.0) })
         {% endif %}
     }
 
@@ -1362,7 +1379,7 @@ impl {{ self_t }} {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> {{ mask_t }} {
@@ -1502,13 +1519,13 @@ impl {{ self_t }} {
 {% if is_float %}
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f32/coresimd/vec3a.rs b/src/f32/coresimd/vec3a.rs
index cee5ce3..ae76912 100644
--- a/src/f32/coresimd/vec3a.rs
+++ b/src/f32/coresimd/vec3a.rs
@@ -394,9 +394,14 @@ impl Vec3A {
     #[inline]
     #[must_use]
     pub fn is_finite(self) -> bool {
-        f32x4::is_finite(self.0)
-            .bitor(mask32x4::from_array([false, false, false, true]))
-            .all()
+        self.is_finite_mask().all()
+    }
+
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec3A {
+        BVec3A(f32x4::is_finite(self.0))
     }
 
     /// Returns `true` if any elements are `NaN`.
@@ -408,7 +413,7 @@ impl Vec3A {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec3A {
@@ -484,13 +489,13 @@ impl Vec3A {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f32/coresimd/vec4.rs b/src/f32/coresimd/vec4.rs
index 1eedfde..f06bac8 100644
--- a/src/f32/coresimd/vec4.rs
+++ b/src/f32/coresimd/vec4.rs
@@ -373,7 +373,14 @@ impl Vec4 {
     #[inline]
     #[must_use]
     pub fn is_finite(self) -> bool {
-        f32x4::is_finite(self.0).all()
+        self.is_finite_mask().all()
+    }
+
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec4A {
+        BVec4A(f32x4::is_finite(self.0))
     }
 
     /// Returns `true` if any elements are `NaN`.
@@ -385,7 +392,7 @@ impl Vec4 {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec4A {
@@ -463,13 +470,13 @@ impl Vec4 {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f32/neon/vec3a.rs b/src/f32/neon/vec3a.rs
index 9a9a2d4..8cb986e 100644
--- a/src/f32/neon/vec3a.rs
+++ b/src/f32/neon/vec3a.rs
@@ -441,7 +441,14 @@ impl Vec3A {
     #[inline]
     #[must_use]
     pub fn is_finite(self) -> bool {
-        self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
+        self.is_finite_mask().all()
+    }
+
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec3A {
+        BVec3A(unsafe { vcltq_f32(vabsq_f32(self.0), Self::INFINITY.0) })
     }
 
     /// Returns `true` if any elements are `NaN`.
@@ -453,7 +460,7 @@ impl Vec3A {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec3A {
@@ -527,13 +534,13 @@ impl Vec3A {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f32/neon/vec4.rs b/src/f32/neon/vec4.rs
index b6e24c9..be7b2e2 100644
--- a/src/f32/neon/vec4.rs
+++ b/src/f32/neon/vec4.rs
@@ -410,7 +410,14 @@ impl Vec4 {
     #[inline]
     #[must_use]
     pub fn is_finite(self) -> bool {
-        self.x.is_finite() && self.y.is_finite() && self.z.is_finite() && self.w.is_finite()
+        self.is_finite_mask().all()
+    }
+
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec4A {
+        BVec4A(unsafe { vcltq_f32(vabsq_f32(self.0), Self::INFINITY.0) })
     }
 
     /// Returns `true` if any elements are `NaN`.
@@ -422,7 +429,7 @@ impl Vec4 {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec4A {
@@ -498,13 +505,13 @@ impl Vec4 {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f32/scalar/vec3a.rs b/src/f32/scalar/vec3a.rs
index f805bea..7186f9e 100644
--- a/src/f32/scalar/vec3a.rs
+++ b/src/f32/scalar/vec3a.rs
@@ -423,6 +423,13 @@ impl Vec3A {
         self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
     }
 
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec3A {
+        BVec3A::new(self.x.is_finite(), self.y.is_finite(), self.z.is_finite())
+    }
+
     /// Returns `true` if any elements are `NaN`.
     #[inline]
     #[must_use]
@@ -432,7 +439,7 @@ impl Vec3A {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec3A {
@@ -506,13 +513,13 @@ impl Vec3A {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f32/scalar/vec4.rs b/src/f32/scalar/vec4.rs
index 60e559a..081cb3b 100644
--- a/src/f32/scalar/vec4.rs
+++ b/src/f32/scalar/vec4.rs
@@ -458,6 +458,18 @@ impl Vec4 {
         self.x.is_finite() && self.y.is_finite() && self.z.is_finite() && self.w.is_finite()
     }
 
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec4A {
+        BVec4A::new(
+            self.x.is_finite(),
+            self.y.is_finite(),
+            self.z.is_finite(),
+            self.w.is_finite(),
+        )
+    }
+
     /// Returns `true` if any elements are `NaN`.
     #[inline]
     #[must_use]
@@ -467,7 +479,7 @@ impl Vec4 {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec4A {
@@ -548,13 +560,13 @@ impl Vec4 {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f32/sse2/vec3a.rs b/src/f32/sse2/vec3a.rs
index 5f9bbe1..c48fdb4 100644
--- a/src/f32/sse2/vec3a.rs
+++ b/src/f32/sse2/vec3a.rs
@@ -431,7 +431,14 @@ impl Vec3A {
     #[inline]
     #[must_use]
     pub fn is_finite(self) -> bool {
-        self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
+        self.is_finite_mask().all()
+    }
+
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec3A {
+        BVec3A(unsafe { _mm_cmplt_ps(crate::sse2::m128_abs(self.0), Self::INFINITY.0) })
     }
 
     /// Returns `true` if any elements are `NaN`.
@@ -443,7 +450,7 @@ impl Vec3A {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec3A {
@@ -523,13 +530,13 @@ impl Vec3A {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f32/sse2/vec4.rs b/src/f32/sse2/vec4.rs
index b755fa9..c272e55 100644
--- a/src/f32/sse2/vec4.rs
+++ b/src/f32/sse2/vec4.rs
@@ -409,7 +409,14 @@ impl Vec4 {
     #[inline]
     #[must_use]
     pub fn is_finite(self) -> bool {
-        self.x.is_finite() && self.y.is_finite() && self.z.is_finite() && self.w.is_finite()
+        self.is_finite_mask().all()
+    }
+
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec4A {
+        BVec4A(unsafe { _mm_cmplt_ps(crate::sse2::m128_abs(self.0), Self::INFINITY.0) })
     }
 
     /// Returns `true` if any elements are `NaN`.
@@ -421,7 +428,7 @@ impl Vec4 {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec4A {
@@ -503,13 +510,13 @@ impl Vec4 {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f32/vec2.rs b/src/f32/vec2.rs
index 561b82c..6f52f8e 100644
--- a/src/f32/vec2.rs
+++ b/src/f32/vec2.rs
@@ -357,6 +357,13 @@ impl Vec2 {
         self.x.is_finite() && self.y.is_finite()
     }
 
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec2 {
+        BVec2::new(self.x.is_finite(), self.y.is_finite())
+    }
+
     /// Returns `true` if any elements are `NaN`.
     #[inline]
     #[must_use]
@@ -366,7 +373,7 @@ impl Vec2 {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec2 {
@@ -438,13 +445,13 @@ impl Vec2 {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f32/vec3.rs b/src/f32/vec3.rs
index b543037..a8b5e68 100644
--- a/src/f32/vec3.rs
+++ b/src/f32/vec3.rs
@@ -413,6 +413,13 @@ impl Vec3 {
         self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
     }
 
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec3 {
+        BVec3::new(self.x.is_finite(), self.y.is_finite(), self.z.is_finite())
+    }
+
     /// Returns `true` if any elements are `NaN`.
     #[inline]
     #[must_use]
@@ -422,7 +429,7 @@ impl Vec3 {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec3 {
@@ -496,13 +503,13 @@ impl Vec3 {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f32/wasm32/vec3a.rs b/src/f32/wasm32/vec3a.rs
index 7781f26..74bb8f6 100644
--- a/src/f32/wasm32/vec3a.rs
+++ b/src/f32/wasm32/vec3a.rs
@@ -405,7 +405,14 @@ impl Vec3A {
     #[inline]
     #[must_use]
     pub fn is_finite(self) -> bool {
-        self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
+        self.is_finite_mask().all()
+    }
+
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec3A {
+        BVec3A(f32x4_lt(f32x4_abs(self.0), Self::INFINITY.0))
     }
 
     /// Returns `true` if any elements are `NaN`.
@@ -417,7 +424,7 @@ impl Vec3A {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec3A {
@@ -493,13 +500,13 @@ impl Vec3A {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f32/wasm32/vec4.rs b/src/f32/wasm32/vec4.rs
index bad1548..5e28a49 100644
--- a/src/f32/wasm32/vec4.rs
+++ b/src/f32/wasm32/vec4.rs
@@ -390,7 +390,14 @@ impl Vec4 {
     #[inline]
     #[must_use]
     pub fn is_finite(self) -> bool {
-        self.x.is_finite() && self.y.is_finite() && self.z.is_finite() && self.w.is_finite()
+        self.is_finite_mask().all()
+    }
+
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec4A {
+        BVec4A(f32x4_lt(f32x4_abs(self.0), Self::INFINITY.0))
     }
 
     /// Returns `true` if any elements are `NaN`.
@@ -402,7 +409,7 @@ impl Vec4 {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec4A {
@@ -480,13 +487,13 @@ impl Vec4 {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f64/dvec2.rs b/src/f64/dvec2.rs
index 993116f..c225b76 100644
--- a/src/f64/dvec2.rs
+++ b/src/f64/dvec2.rs
@@ -357,6 +357,13 @@ impl DVec2 {
         self.x.is_finite() && self.y.is_finite()
     }
 
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec2 {
+        BVec2::new(self.x.is_finite(), self.y.is_finite())
+    }
+
     /// Returns `true` if any elements are `NaN`.
     #[inline]
     #[must_use]
@@ -366,7 +373,7 @@ impl DVec2 {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec2 {
@@ -438,13 +445,13 @@ impl DVec2 {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f64/dvec3.rs b/src/f64/dvec3.rs
index aefccb3..9ad3da5 100644
--- a/src/f64/dvec3.rs
+++ b/src/f64/dvec3.rs
@@ -413,6 +413,13 @@ impl DVec3 {
         self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
     }
 
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec3 {
+        BVec3::new(self.x.is_finite(), self.y.is_finite(), self.z.is_finite())
+    }
+
     /// Returns `true` if any elements are `NaN`.
     #[inline]
     #[must_use]
@@ -422,7 +429,7 @@ impl DVec3 {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec3 {
@@ -496,13 +503,13 @@ impl DVec3 {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {
diff --git a/src/f64/dvec4.rs b/src/f64/dvec4.rs
index 1d3f478..ec4bd01 100644
--- a/src/f64/dvec4.rs
+++ b/src/f64/dvec4.rs
@@ -447,6 +447,18 @@ impl DVec4 {
         self.x.is_finite() && self.y.is_finite() && self.z.is_finite() && self.w.is_finite()
     }
 
+    /// Performs `is_finite` on each element of self, returning a vector mask of the results.
+    ///
+    /// In other words, this computes `[x.is_finite(), y.is_finite(), ...]`.
+    pub fn is_finite_mask(self) -> BVec4 {
+        BVec4::new(
+            self.x.is_finite(),
+            self.y.is_finite(),
+            self.z.is_finite(),
+            self.w.is_finite(),
+        )
+    }
+
     /// Returns `true` if any elements are `NaN`.
     #[inline]
     #[must_use]
@@ -456,7 +468,7 @@ impl DVec4 {
 
     /// Performs `is_nan` on each element of self, returning a vector mask of the results.
     ///
-    /// In other words, this computes `[x.is_nan(), y.is_nan(), z.is_nan(), w.is_nan()]`.
+    /// In other words, this computes `[x.is_nan(), y.is_nan(), ...]`.
     #[inline]
     #[must_use]
     pub fn is_nan_mask(self) -> BVec4 {
@@ -537,13 +549,13 @@ impl DVec4 {
 
     /// Returns `self` normalized to length 1.0.
     ///
-    /// For valid results, `self` must _not_ be of length zero, nor very close to zero.
+    /// For valid results, `self` must be finite and _not_ of length zero, nor very close to zero.
     ///
     /// See also [`Self::try_normalize()`] and [`Self::normalize_or_zero()`].
     ///
     /// Panics
     ///
-    /// Will panic if `self` is zero length when `glam_assert` is enabled.
+    /// Will panic if the resulting normalized vector is not finite when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn normalize(self) -> Self {


Test patch:
diff --git a/tests/quat.rs b/tests/quat.rs
index 2fbdf00..2e73554 100644
--- a/tests/quat.rs
+++ b/tests/quat.rs
@@ -327,7 +327,7 @@ macro_rules! impl_quat_tests {
 
         glam_test!(test_rotate_towards, {
             use core::$t::consts::{FRAC_PI_2, FRAC_PI_4};
-            let eps = 10.0 * core::$t::EPSILON as f32;
+            let eps = 10.0 * $t::EPSILON as f32;
 
             // Setup such that `q0` is `PI/2` and `-PI/2` radians away from `q1` and `q2` respectively.
             let q0 = $quat::from_euler(EulerRot::YXZ, 0.0, 0.0, 0.0);
diff --git a/tests/vec2.rs b/tests/vec2.rs
index 977d080..c63d5c4 100644
--- a/tests/vec2.rs
+++ b/tests/vec2.rs
@@ -840,7 +840,7 @@ macro_rules! impl_vec2_float_tests {
 
         glam_test!(test_rotate_towards, {
             use core::$t::consts::{FRAC_PI_2, FRAC_PI_4};
-            let eps = 10.0 * core::$t::EPSILON as f32;
+            let eps = 10.0 * $t::EPSILON as f32;
 
             // Setup such that `v0` is `PI/2` and `-PI/2` radians away from `v1` and `v2` respectively.
             let v0 = $vec2::new(1.0, 0.0);