Repository: bitshifter/glam-rs
Pull request #405 (2023-05-27 05:35:44)

Add `trunc` method to float vector types.


Patch:
diff --git a/codegen/templates/vec.rs.tera b/codegen/templates/vec.rs.tera
index fef4ccd..76a4411 100644
--- a/codegen/templates/vec.rs.tera
+++ b/codegen/templates/vec.rs.tera
@@ -1278,6 +1278,25 @@ impl {{ self_t }} {
         {% endif %}
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        {% if is_scalar %}
+            Self {
+                {% for c in components %}
+                    {{ c }}: math::trunc(self.{{ c }}),
+                {%- endfor %}
+            }
+        {% elif is_sse2 %}
+            Self(unsafe { m128_trunc(self.0) })
+        {% elif is_wasm32 %}
+            Self(f32x4_trunc(self.0))
+        {% elif is_coresimd %}
+            Self(self.0.trunc())
+        {% endif %}
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f32/coresimd/vec3a.rs b/src/f32/coresimd/vec3a.rs
index 648a5cc..21c9466 100644
--- a/src/f32/coresimd/vec3a.rs
+++ b/src/f32/coresimd/vec3a.rs
@@ -523,6 +523,13 @@ impl Vec3A {
         Self(self.0.ceil())
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self(self.0.trunc())
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f32/coresimd/vec4.rs b/src/f32/coresimd/vec4.rs
index fed9a92..79435ac 100644
--- a/src/f32/coresimd/vec4.rs
+++ b/src/f32/coresimd/vec4.rs
@@ -496,6 +496,13 @@ impl Vec4 {
         Self(self.0.ceil())
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self(self.0.trunc())
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f32/math.rs b/src/f32/math.rs
index 6a71826..36b891a 100644
--- a/src/f32/math.rs
+++ b/src/f32/math.rs
@@ -100,6 +100,11 @@ mod libm_math {
         libm::roundf(f)
     }
 
+    #[inline(always)]
+    pub(crate) fn trunc(f: f32) -> f32 {
+        libm::truncf(f)
+    }
+
     #[inline(always)]
     pub(crate) fn ceil(f: f32) -> f32 {
         libm::ceilf(f)
@@ -184,6 +189,11 @@ mod std_math {
         f32::round(f)
     }
 
+    #[inline(always)]
+    pub(crate) fn trunc(f: f32) -> f32 {
+        f32::trunc(f)
+    }
+
     #[inline(always)]
     pub(crate) fn ceil(f: f32) -> f32 {
         f32::ceil(f)
diff --git a/src/f32/scalar/vec3a.rs b/src/f32/scalar/vec3a.rs
index b1e0ca2..7797a86 100644
--- a/src/f32/scalar/vec3a.rs
+++ b/src/f32/scalar/vec3a.rs
@@ -556,6 +556,17 @@ impl Vec3A {
         }
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self {
+            x: math::trunc(self.x),
+            y: math::trunc(self.y),
+            z: math::trunc(self.z),
+        }
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f32/scalar/vec4.rs b/src/f32/scalar/vec4.rs
index adca7a5..2859832 100644
--- a/src/f32/scalar/vec4.rs
+++ b/src/f32/scalar/vec4.rs
@@ -590,6 +590,18 @@ impl Vec4 {
         }
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self {
+            x: math::trunc(self.x),
+            y: math::trunc(self.y),
+            z: math::trunc(self.z),
+            w: math::trunc(self.w),
+        }
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f32/sse2/vec3a.rs b/src/f32/sse2/vec3a.rs
index b6e0ffd..fc7db8d 100644
--- a/src/f32/sse2/vec3a.rs
+++ b/src/f32/sse2/vec3a.rs
@@ -561,6 +561,13 @@ impl Vec3A {
         Self(unsafe { m128_ceil(self.0) })
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self(unsafe { m128_trunc(self.0) })
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f32/sse2/vec4.rs b/src/f32/sse2/vec4.rs
index c060638..cfbdebe 100644
--- a/src/f32/sse2/vec4.rs
+++ b/src/f32/sse2/vec4.rs
@@ -535,6 +535,13 @@ impl Vec4 {
         Self(unsafe { m128_ceil(self.0) })
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self(unsafe { m128_trunc(self.0) })
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f32/vec2.rs b/src/f32/vec2.rs
index 0e36223..94d3f0a 100644
--- a/src/f32/vec2.rs
+++ b/src/f32/vec2.rs
@@ -499,6 +499,16 @@ impl Vec2 {
         }
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self {
+            x: math::trunc(self.x),
+            y: math::trunc(self.y),
+        }
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f32/vec3.rs b/src/f32/vec3.rs
index db241ec..ace8db1 100644
--- a/src/f32/vec3.rs
+++ b/src/f32/vec3.rs
@@ -547,6 +547,17 @@ impl Vec3 {
         }
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self {
+            x: math::trunc(self.x),
+            y: math::trunc(self.y),
+            z: math::trunc(self.z),
+        }
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f32/wasm32/vec3a.rs b/src/f32/wasm32/vec3a.rs
index db1a710..4be767a 100644
--- a/src/f32/wasm32/vec3a.rs
+++ b/src/f32/wasm32/vec3a.rs
@@ -536,6 +536,13 @@ impl Vec3A {
         Self(f32x4_ceil(self.0))
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self(f32x4_trunc(self.0))
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f32/wasm32/vec4.rs b/src/f32/wasm32/vec4.rs
index a74bacd..eff0fc0 100644
--- a/src/f32/wasm32/vec4.rs
+++ b/src/f32/wasm32/vec4.rs
@@ -517,6 +517,13 @@ impl Vec4 {
         Self(f32x4_ceil(self.0))
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self(f32x4_trunc(self.0))
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f64/dvec2.rs b/src/f64/dvec2.rs
index b7b2903..7607b00 100644
--- a/src/f64/dvec2.rs
+++ b/src/f64/dvec2.rs
@@ -499,6 +499,16 @@ impl DVec2 {
         }
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self {
+            x: math::trunc(self.x),
+            y: math::trunc(self.y),
+        }
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f64/dvec3.rs b/src/f64/dvec3.rs
index b1f34d0..6b88877 100644
--- a/src/f64/dvec3.rs
+++ b/src/f64/dvec3.rs
@@ -547,6 +547,17 @@ impl DVec3 {
         }
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self {
+            x: math::trunc(self.x),
+            y: math::trunc(self.y),
+            z: math::trunc(self.z),
+        }
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f64/dvec4.rs b/src/f64/dvec4.rs
index 167cb2e..ecff505 100644
--- a/src/f64/dvec4.rs
+++ b/src/f64/dvec4.rs
@@ -582,6 +582,18 @@ impl DVec4 {
         }
     }
 
+    /// Returns a vector containing the integer part each element of `self`. This means numbers are
+    /// always truncated towards zero.
+    #[inline]
+    pub fn trunc(self) -> Self {
+        Self {
+            x: math::trunc(self.x),
+            y: math::trunc(self.y),
+            z: math::trunc(self.z),
+            w: math::trunc(self.w),
+        }
+    }
+
     /// Returns a vector containing the fractional part of the vector, e.g. `self -
     /// self.floor()`.
     ///
diff --git a/src/f64/math.rs b/src/f64/math.rs
index 309be33..3454342 100644
--- a/src/f64/math.rs
+++ b/src/f64/math.rs
@@ -65,6 +65,11 @@ mod libm_math {
         libm::round(f)
     }
 
+    #[inline(always)]
+    pub(crate) fn trunc(f: f64) -> f64 {
+        libm::trunc(f)
+    }
+
     #[inline(always)]
     pub(crate) fn ceil(f: f64) -> f64 {
         libm::ceil(f)
@@ -148,6 +153,11 @@ mod std_math {
         f64::round(f)
     }
 
+    #[inline(always)]
+    pub(crate) fn trunc(f: f64) -> f64 {
+        f64::trunc(f)
+    }
+
     #[inline(always)]
     pub(crate) fn ceil(f: f64) -> f64 {
         f64::ceil(f)
diff --git a/src/sse2.rs b/src/sse2.rs
index 02c3efc..d5dcdbc 100644
--- a/src/sse2.rs
+++ b/src/sse2.rs
@@ -18,6 +18,7 @@ const fn m128_from_u32x4(u32x4: [u32; 4]) -> __m128 {
     unsafe { UnionCast { u32x4 }.m128 }
 }
 
+const PS_ABS_MASK: __m128 = m128_from_u32x4([0x7fffffff; 4]);
 const PS_INV_SIGN_MASK: __m128 = m128_from_u32x4([!0x8000_0000; 4]);
 const PS_SIGN_MASK: __m128 = m128_from_u32x4([0x8000_0000; 4]);
 const PS_NO_FRACTION: __m128 = m128_from_f32x4([8388608.0; 4]);
@@ -157,6 +158,26 @@ pub(crate) unsafe fn m128_round(v: __m128) -> __m128 {
     _mm_xor_ps(r1, r2)
 }
 
+#[inline]
+pub(crate) unsafe fn m128_trunc(v: __m128) -> __m128 {
+    // Based on https://github.com/microsoft/DirectXMath `XMVectorTruncate`
+    // To handle NAN, INF and numbers greater than 8388608, use masking
+    // Get the abs value
+    let mut vtest = _mm_and_si128(_mm_castps_si128(v), _mm_castps_si128(PS_ABS_MASK));
+    // Test for greater than 8388608 (All floats with NO fractionals, NAN and INF
+    vtest = _mm_cmplt_epi32(vtest, _mm_castps_si128(PS_NO_FRACTION));
+    // Convert to int and back to float for rounding with truncation
+    let vint = _mm_cvttps_epi32(v);
+    // Convert back to floats
+    let mut vresult = _mm_cvtepi32_ps(vint);
+    // All numbers less than 8388608 will use the round to int
+    vresult = _mm_and_ps(vresult, _mm_castsi128_ps(vtest));
+    // All others, use the ORIGINAL value
+    vtest = _mm_andnot_si128(vtest, _mm_castps_si128(v));
+    vresult = _mm_or_ps(vresult, _mm_castsi128_ps(vtest));
+    return vresult;
+}
+
 /// Returns a vector whose components are the corresponding components of Angles modulo 2PI.
 #[inline]
 pub(crate) unsafe fn m128_mod_angles(angles: __m128) -> __m128 {


Test patch:
diff --git a/tests/vec2.rs b/tests/vec2.rs
index 0672307..195995a 100644
--- a/tests/vec2.rs
+++ b/tests/vec2.rs
@@ -738,6 +738,19 @@ macro_rules! impl_vec2_float_tests {
             );
         });
 
+        glam_test!(test_trunc, {
+            assert_eq!($vec2::new(1.35, -1.5).trunc(), $vec2::new(1.0, -1.0));
+            assert_eq!(
+                $vec2::new(INFINITY, NEG_INFINITY).trunc(),
+                $vec2::new(INFINITY, NEG_INFINITY)
+            );
+            assert!($vec2::new(0.0, NAN).trunc().y.is_nan());
+            assert_eq!(
+                $vec2::new(-0.0, -2000000.123).trunc(),
+                $vec2::new(-0.0, -2000000.0)
+            );
+        });
+
         glam_test!(test_lerp, {
             let v0 = $vec2::new(-1.0, -1.0);
             let v1 = $vec2::new(1.0, 1.0);
diff --git a/tests/vec3.rs b/tests/vec3.rs
index a4dc5eb..f8faf1d 100644
--- a/tests/vec3.rs
+++ b/tests/vec3.rs
@@ -822,6 +822,22 @@ macro_rules! impl_vec3_float_tests {
             );
         });
 
+        glam_test!(test_trunc, {
+            assert_eq!(
+                $vec3::new(1.35, 1.5, -1.5).trunc(),
+                $vec3::new(1.0, 1.0, -1.0)
+            );
+            assert_eq!(
+                $vec3::new(INFINITY, NEG_INFINITY, 0.0).trunc(),
+                $vec3::new(INFINITY, NEG_INFINITY, 0.0)
+            );
+            assert!($vec3::new(0.0, NAN, 0.0).trunc().y.is_nan());
+            assert_eq!(
+                $vec3::new(-0.0, -2000000.123, 10000000.123).trunc(),
+                $vec3::new(-0.0, -2000000.0, 10000000.0)
+            );
+        });
+
         glam_test!(test_lerp, {
             let v0 = $vec3::new(-1.0, -1.0, -1.0);
             let v1 = $vec3::new(1.0, 1.0, 1.0);
diff --git a/tests/vec4.rs b/tests/vec4.rs
index 03ba660..e414752 100644
--- a/tests/vec4.rs
+++ b/tests/vec4.rs
@@ -925,6 +925,22 @@ macro_rules! impl_vec4_float_tests {
             );
         });
 
+        glam_test!(test_trunc, {
+            assert_eq!(
+                $vec4::new(1.35, 1.5, -1.5, 1.999).trunc(),
+                $vec4::new(1.0, 1.0, -1.0, 1.0)
+            );
+            assert_eq!(
+                $vec4::new(INFINITY, NEG_INFINITY, 0.0, 0.0).trunc(),
+                $vec4::new(INFINITY, NEG_INFINITY, 0.0, 0.0)
+            );
+            assert!($vec4::new(0.0, NAN, 0.0, 0.0).trunc().y.is_nan());
+            assert_eq!(
+                $vec4::new(-0.0, -2000000.123, 10000000.123, 1000.9).trunc(),
+                $vec4::new(-0.0, -2000000.0, 10000000.0, 1000.0)
+            );
+        });
+
         glam_test!(test_lerp, {
             let v0 = $vec4::new(-1.0, -1.0, -1.0, -1.0);
             let v1 = $vec4::new(1.0, 1.0, 1.0, 1.0);