Repository: bitshifter/glam-rs
Pull request #345 (2022-09-29 02:20:30)

Add Vec::sign_bits
This is dependent on the fixes in #344 (those problems were found while adding this function)

I need this function for optimized tetrahedron face winding order checks (four dot products being put in a `Vec` and then grabbing the sign bits to check if its either `0b0000` or `0b1111`, thus having all the same winding order and not being degenerate) among other things.

`BVec::bitmask` almost does this, but gets the LSB for scalar implementations, but I need it on `Vec` anyways so its irrelevant

bitshifter: I'm wondering what the most general purpose method would be for most users here.

For example, there are already methods on `VecN` for `is_finiite -> bool` (all elements finite) and `is_nan -> bool` (any elements are nan). I've tried to match the naming use on `f32`/`f64` there although I guess it's not entirely consistent about whether the result is `any` or `all`. There is also `is_nan_mask` which returns `BVecN`.

For your usage it seems like a `is_sign_negative` which returns `true` if `all` elements are sign negative would fit your use case? That would probably fit better with the existing naming scheme. Do you need a `u32` mask beyond checking if it's all zeros or all ones?
rodolphito: My use-case needs to distinguish between three cases: all positive, all negative, or mixed signs. I don't need to know what the specific mix is if its mixed. Sample code:

```
    match sign_winding.sign_bits();
    {
        // All positive
        0 => sign_origin.cmpge(Vec4::splat(-f32::EPSILON)),

        // All negative
        0xf => sign_origin.cmple(Vec4::splat(f32::EPSILON)),

        // Mixed signs, degenerate tetrahedron
        _ => BVec4A::splat(true)
    }
```
bitshifter: Kind of a note to myself, it might be good to add `TRUE` and `FALSE` constants to `BVecN` types for this kind of use.
rodolphito: Funny, there's a FALSE const for all BVecN types but no TRUE. I can PR this if its convenient
bitshifter: The current false is private I think. If you want to make a PR, go for it.

Patch:
diff --git a/codegen/templates/vec.rs.tera b/codegen/templates/vec.rs.tera
index 18506be..425262e 100644
--- a/codegen/templates/vec.rs.tera
+++ b/codegen/templates/vec.rs.tera
@@ -825,6 +825,49 @@ impl {{ self_t }} {
             }
         {% endif %}
     }
+
+    /// Returns a bitmask with the lowest {{ dim }} bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        {% if is_scalar and is_float %}
+            {% for c in components %}
+                {% if loop.first %}
+                    (self.{{ c }}.is_sign_negative() as u32) |
+                {% else %}
+                    (self.{{ c }}.is_sign_negative() as u32) << {{ loop.index0 }} {% if not loop.last %} | {% endif %}
+                {% endif %}
+            {% endfor %}
+        {% elif is_scalar %}
+            {% for c in components %}
+                {% if loop.first %}
+                    (self.{{ c }}.is_negative() as u32) |
+                {% else %}
+                    (self.{{ c }}.is_negative() as u32) << {{ loop.index0 }} {% if not loop.last %} | {% endif %}
+                {% endif %}
+            {% endfor %}
+        {% elif is_sse2 %}
+            {% if dim == 3 %}
+                unsafe { (_mm_movemask_ps(self.0) as u32) & 0x7 }
+            {% elif dim == 4 %}
+                unsafe { _mm_movemask_ps(self.0) as u32 }
+            {% endif %}
+        {% elif is_wasm32 %}
+            {% if dim == 3 %}
+                (u32x4_bitmask(self.0) & 0x7) as u32
+            {% elif dim == 4 %}
+                u32x4_bitmask(self.0) as u32
+            {% endif %}
+        {% elif is_coresimd %}
+            {% if dim == 3 %}
+                (self.0.is_sign_negative().to_bitmask() & 0x7) as u32
+            {% elif dim == 4 %}
+                self.0.is_sign_negative().to_bitmask() as u32
+            {% endif %}
+        {% endif %}
+    }
 {% endif %}
 
 {% if is_float %}
diff --git a/src/f32/coresimd/vec3a.rs b/src/f32/coresimd/vec3a.rs
index 966217e..71f5c64 100644
--- a/src/f32/coresimd/vec3a.rs
+++ b/src/f32/coresimd/vec3a.rs
@@ -292,6 +292,15 @@ impl Vec3A {
         Self(self.0.signum())
     }
 
+    /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        (self.0.is_sign_negative().to_bitmask() & 0x7) as u32
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/f32/coresimd/vec4.rs b/src/f32/coresimd/vec4.rs
index 38b2d4f..fa92f93 100644
--- a/src/f32/coresimd/vec4.rs
+++ b/src/f32/coresimd/vec4.rs
@@ -268,6 +268,15 @@ impl Vec4 {
         Self(self.0.signum())
     }
 
+    /// Returns a bitmask with the lowest 4 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        self.0.is_sign_negative().to_bitmask() as u32
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/f32/scalar/vec3a.rs b/src/f32/scalar/vec3a.rs
index 00c1e8f..26b82b6 100644
--- a/src/f32/scalar/vec3a.rs
+++ b/src/f32/scalar/vec3a.rs
@@ -312,6 +312,17 @@ impl Vec3A {
         }
     }
 
+    /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        (self.x.is_sign_negative() as u32)
+            | (self.y.is_sign_negative() as u32) << 1
+            | (self.z.is_sign_negative() as u32) << 2
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/f32/scalar/vec4.rs b/src/f32/scalar/vec4.rs
index db0a60a..6abcccd 100644
--- a/src/f32/scalar/vec4.rs
+++ b/src/f32/scalar/vec4.rs
@@ -339,6 +339,18 @@ impl Vec4 {
         }
     }
 
+    /// Returns a bitmask with the lowest 4 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        (self.x.is_sign_negative() as u32)
+            | (self.y.is_sign_negative() as u32) << 1
+            | (self.z.is_sign_negative() as u32) << 2
+            | (self.w.is_sign_negative() as u32) << 3
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/f32/sse2/vec3a.rs b/src/f32/sse2/vec3a.rs
index ace0933..56b8c8d 100644
--- a/src/f32/sse2/vec3a.rs
+++ b/src/f32/sse2/vec3a.rs
@@ -319,6 +319,15 @@ impl Vec3A {
         }
     }
 
+    /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        unsafe { (_mm_movemask_ps(self.0) as u32) & 0x7 }
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/f32/sse2/vec4.rs b/src/f32/sse2/vec4.rs
index 906f78a..f72e027 100644
--- a/src/f32/sse2/vec4.rs
+++ b/src/f32/sse2/vec4.rs
@@ -294,6 +294,15 @@ impl Vec4 {
         }
     }
 
+    /// Returns a bitmask with the lowest 4 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        unsafe { _mm_movemask_ps(self.0) as u32 }
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/f32/vec2.rs b/src/f32/vec2.rs
index ab9ec46..13181e1 100644
--- a/src/f32/vec2.rs
+++ b/src/f32/vec2.rs
@@ -264,6 +264,15 @@ impl Vec2 {
         }
     }
 
+    /// Returns a bitmask with the lowest 2 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        (self.x.is_sign_negative() as u32) | (self.y.is_sign_negative() as u32) << 1
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/f32/vec3.rs b/src/f32/vec3.rs
index 3f745c0..5606263 100644
--- a/src/f32/vec3.rs
+++ b/src/f32/vec3.rs
@@ -306,6 +306,17 @@ impl Vec3 {
         }
     }
 
+    /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        (self.x.is_sign_negative() as u32)
+            | (self.y.is_sign_negative() as u32) << 1
+            | (self.z.is_sign_negative() as u32) << 2
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/f32/wasm32/vec3a.rs b/src/f32/wasm32/vec3a.rs
index 4ee5d7e..08a6269 100644
--- a/src/f32/wasm32/vec3a.rs
+++ b/src/f32/wasm32/vec3a.rs
@@ -300,6 +300,15 @@ impl Vec3A {
         }
     }
 
+    /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        (u32x4_bitmask(self.0) & 0x7) as u32
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/f32/wasm32/vec4.rs b/src/f32/wasm32/vec4.rs
index 1fc475b..de16f66 100644
--- a/src/f32/wasm32/vec4.rs
+++ b/src/f32/wasm32/vec4.rs
@@ -282,6 +282,15 @@ impl Vec4 {
         }
     }
 
+    /// Returns a bitmask with the lowest 4 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        u32x4_bitmask(self.0) as u32
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/f64/dvec2.rs b/src/f64/dvec2.rs
index 3fdfc44..42039e9 100644
--- a/src/f64/dvec2.rs
+++ b/src/f64/dvec2.rs
@@ -264,6 +264,15 @@ impl DVec2 {
         }
     }
 
+    /// Returns a bitmask with the lowest 2 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        (self.x.is_sign_negative() as u32) | (self.y.is_sign_negative() as u32) << 1
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/f64/dvec3.rs b/src/f64/dvec3.rs
index d7bc1a2..0260d42 100644
--- a/src/f64/dvec3.rs
+++ b/src/f64/dvec3.rs
@@ -306,6 +306,17 @@ impl DVec3 {
         }
     }
 
+    /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        (self.x.is_sign_negative() as u32)
+            | (self.y.is_sign_negative() as u32) << 1
+            | (self.z.is_sign_negative() as u32) << 2
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/f64/dvec4.rs b/src/f64/dvec4.rs
index 21a60ce..50d49b4 100644
--- a/src/f64/dvec4.rs
+++ b/src/f64/dvec4.rs
@@ -331,6 +331,18 @@ impl DVec4 {
         }
     }
 
+    /// Returns a bitmask with the lowest 4 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        (self.x.is_sign_negative() as u32)
+            | (self.y.is_sign_negative() as u32) << 1
+            | (self.z.is_sign_negative() as u32) << 2
+            | (self.w.is_sign_negative() as u32) << 3
+    }
+
     /// Returns `true` if, and only if, all elements are finite.  If any element is either
     /// `NaN`, positive or negative infinity, this will return `false`.
     #[inline]
diff --git a/src/i32/ivec2.rs b/src/i32/ivec2.rs
index fc21896..599f4f5 100644
--- a/src/i32/ivec2.rs
+++ b/src/i32/ivec2.rs
@@ -258,6 +258,15 @@ impl IVec2 {
         }
     }
 
+    /// Returns a bitmask with the lowest 2 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        (self.x.is_negative() as u32) | (self.y.is_negative() as u32) << 1
+    }
+
     /// Returns a vector that is equal to `self` rotated by 90 degrees.
     #[inline]
     pub fn perp(self) -> Self {
diff --git a/src/i32/ivec3.rs b/src/i32/ivec3.rs
index 1d14108..7a87a30 100644
--- a/src/i32/ivec3.rs
+++ b/src/i32/ivec3.rs
@@ -300,6 +300,17 @@ impl IVec3 {
         }
     }
 
+    /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        (self.x.is_negative() as u32)
+            | (self.y.is_negative() as u32) << 1
+            | (self.z.is_negative() as u32) << 2
+    }
+
     /// Casts all elements of `self` to `f32`.
     #[inline]
     pub fn as_vec3(&self) -> crate::Vec3 {
diff --git a/src/i32/ivec4.rs b/src/i32/ivec4.rs
index 449f1b9..157cf05 100644
--- a/src/i32/ivec4.rs
+++ b/src/i32/ivec4.rs
@@ -325,6 +325,18 @@ impl IVec4 {
         }
     }
 
+    /// Returns a bitmask with the lowest 4 bits set to the sign bits from the elements of `self`.
+    ///
+    /// A negative element results in a `1` bit and a positive element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn is_negative_bitmask(self) -> u32 {
+        (self.x.is_negative() as u32)
+            | (self.y.is_negative() as u32) << 1
+            | (self.z.is_negative() as u32) << 2
+            | (self.w.is_negative() as u32) << 3
+    }
+
     /// Casts all elements of `self` to `f32`.
     #[inline]
     pub fn as_vec4(&self) -> crate::Vec4 {


Test patch:
diff --git a/tests/vec2.rs b/tests/vec2.rs
index 8a4163c..3ca9acc 100644
--- a/tests/vec2.rs
+++ b/tests/vec2.rs
@@ -606,6 +606,17 @@ macro_rules! impl_vec2_float_tests {
             assert!($vec2::splat(NAN).signum().is_nan_mask().all());
         });
 
+        glam_test!(test_is_negative_bitmask, {
+            assert_eq!($vec2::ZERO.is_negative_bitmask(), 0b00);
+            assert_eq!((-$vec2::ZERO).is_negative_bitmask(), 0b11);
+            assert_eq!($vec2::ONE.is_negative_bitmask(), 0b00);
+            assert_eq!((-$vec2::ONE).is_negative_bitmask(), 0b11);
+            assert_eq!($vec2::new(-0.1, 0.2).is_negative_bitmask(), 0b01);
+            assert_eq!($vec2::new(0.8, 0.3).is_negative_bitmask(), 0b00);
+            assert_eq!($vec2::new(0.3, -0.4).is_negative_bitmask(), 0b10);
+            assert_eq!($vec2::new(-0.2, -0.6).is_negative_bitmask(), 0b11);
+        });
+
         glam_test!(test_abs, {
             assert_eq!($vec2::ZERO.abs(), $vec2::ZERO);
             assert_eq!($vec2::ONE.abs(), $vec2::ONE);
diff --git a/tests/vec3.rs b/tests/vec3.rs
index 095264b..92af042 100644
--- a/tests/vec3.rs
+++ b/tests/vec3.rs
@@ -668,6 +668,18 @@ macro_rules! impl_vec3_float_tests {
             assert!($vec3::splat(NAN).signum().is_nan_mask().all());
         });
 
+        glam_test!(test_is_negative_bitmask, {
+            assert_eq!($vec3::ZERO.is_negative_bitmask(), 0b000);
+            assert_eq!((-$vec3::ZERO).is_negative_bitmask(), 0b111);
+            assert_eq!($vec3::ONE.is_negative_bitmask(), 0b000);
+            assert_eq!((-$vec3::ONE).is_negative_bitmask(), 0b111);
+            assert_eq!($vec3::new(-0.1, 0.2, 0.3).is_negative_bitmask(), 0b001);
+            assert_eq!($vec3::new(0.8, 0.3, 0.1).is_negative_bitmask(), 0b000);
+            assert_eq!($vec3::new(0.1, 0.5, -0.3).is_negative_bitmask(), 0b100);
+            assert_eq!($vec3::new(0.3, -0.4, 0.1).is_negative_bitmask(), 0b010);
+            assert_eq!($vec3::new(-0.2, 0.6, -0.5).is_negative_bitmask(), 0b101);
+        });
+
         glam_test!(test_abs, {
             assert_eq!($vec3::ZERO.abs(), $vec3::ZERO);
             assert_eq!($vec3::ONE.abs(), $vec3::ONE);
diff --git a/tests/vec4.rs b/tests/vec4.rs
index 2646727..9d06371 100644
--- a/tests/vec4.rs
+++ b/tests/vec4.rs
@@ -750,6 +750,33 @@ macro_rules! impl_vec4_float_tests {
             assert!($vec4::splat(NAN).signum().is_nan_mask().all());
         });
 
+        glam_test!(test_is_negative_bitmask, {
+            assert_eq!($vec4::ZERO.is_negative_bitmask(), 0b0000);
+            assert_eq!((-$vec4::ZERO).is_negative_bitmask(), 0b1111);
+            assert_eq!($vec4::ONE.is_negative_bitmask(), 0b0000);
+            assert_eq!((-$vec4::ONE).is_negative_bitmask(), 0b1111);
+            assert_eq!(
+                $vec4::new(-0.1, 0.2, 0.3, -0.4).is_negative_bitmask(),
+                0b1001
+            );
+            assert_eq!(
+                $vec4::new(0.8, 0.3, 0.1, -0.0).is_negative_bitmask(),
+                0b1000
+            );
+            assert_eq!(
+                $vec4::new(0.1, 0.5, -0.3, 0.7).is_negative_bitmask(),
+                0b0100
+            );
+            assert_eq!(
+                $vec4::new(0.3, -0.4, 0.1, 0.6).is_negative_bitmask(),
+                0b0010
+            );
+            assert_eq!(
+                $vec4::new(0.2, -0.6, 0.5, -0.3).is_negative_bitmask(),
+                0b1010
+            );
+        });
+
         glam_test!(test_abs, {
             assert_eq!($vec4::ZERO.abs(), $vec4::ZERO);
             assert_eq!($vec4::ONE.abs(), $vec4::ONE);