Repository: bitshifter/glam-rs (rust)

Issue:
Vec3A projection convenience function
I noticed that most of the Mat4 convenience methods have Vec3 and Vec3A counterparts.
* transform_point3 / transform_point3a
* transform_vector3 / transform_vector3a
* project_point3 / ?

Is there any particular reason that project_point3a doesn't exist?

Pull request description:
Add `Mat4::project_point3a`.
Fixes #568.

Patch:
diff --git a/codegen/templates/mat.rs.tera b/codegen/templates/mat.rs.tera
index 1ad3658..47ab23e 100644
--- a/codegen/templates/mat.rs.tera
+++ b/codegen/templates/mat.rs.tera
@@ -1989,7 +1989,7 @@ impl {{ self_t }} {
         res = self.y_axis.mul(rhs.y).add(res);
         res = self.z_axis.mul(rhs.z).add(res);
         res = self.w_axis.add(res);
-        res = res.mul(res.wwww().recip());
+        res = res.div(res.w);
         res.xyz()
     }
 
@@ -2039,6 +2039,27 @@ impl {{ self_t }} {
 {% endif %}
 
 {% if self_t == "Mat4" %}
+    /// Transforms the given [`Vec3A`] as a 3D point, applying perspective correction.
+    ///
+    /// This is the equivalent of multiplying the [`Vec3A`] as a 4D vector where `w` is `1.0`.
+    /// The perspective divide is performed meaning the resulting 3D vector is divided by `w`.
+    ///
+    /// This method assumes that `self` contains a projective transform.
+    #[inline]
+    #[must_use]
+    pub fn project_point3a(&self, rhs: Vec3A) -> Vec3A {
+        {% if is_scalar %}
+            self.project_point3(rhs.into()).into()
+        {% else %}
+            let mut res = self.x_axis.mul(rhs.xxxx());
+            res = self.y_axis.mul(rhs.yyyy()).add(res);
+            res = self.z_axis.mul(rhs.zzzz()).add(res);
+            res = self.w_axis.add(res);
+            res = res.div(res.wwww());
+            Vec3A::from_vec4(res)
+        {% endif %}
+    }
+
     /// Transforms the given [`Vec3A`] as 3D point.
     ///
     /// This is the equivalent of multiplying the [`Vec3A`] as a 4D vector where `w` is `1.0`.
diff --git a/src/f32/coresimd/mat4.rs b/src/f32/coresimd/mat4.rs
index f39c7d8..6082992 100644
--- a/src/f32/coresimd/mat4.rs
+++ b/src/f32/coresimd/mat4.rs
@@ -1121,7 +1121,7 @@ impl Mat4 {
         res = self.y_axis.mul(rhs.y).add(res);
         res = self.z_axis.mul(rhs.z).add(res);
         res = self.w_axis.add(res);
-        res = res.mul(res.wwww().recip());
+        res = res.div(res.w);
         res.xyz()
     }
 
@@ -1168,6 +1168,23 @@ impl Mat4 {
         res.xyz()
     }
 
+    /// Transforms the given [`Vec3A`] as a 3D point, applying perspective correction.
+    ///
+    /// This is the equivalent of multiplying the [`Vec3A`] as a 4D vector where `w` is `1.0`.
+    /// The perspective divide is performed meaning the resulting 3D vector is divided by `w`.
+    ///
+    /// This method assumes that `self` contains a projective transform.
+    #[inline]
+    #[must_use]
+    pub fn project_point3a(&self, rhs: Vec3A) -> Vec3A {
+        let mut res = self.x_axis.mul(rhs.xxxx());
+        res = self.y_axis.mul(rhs.yyyy()).add(res);
+        res = self.z_axis.mul(rhs.zzzz()).add(res);
+        res = self.w_axis.add(res);
+        res = res.div(res.wwww());
+        Vec3A::from_vec4(res)
+    }
+
     /// Transforms the given [`Vec3A`] as 3D point.
     ///
     /// This is the equivalent of multiplying the [`Vec3A`] as a 4D vector where `w` is `1.0`.
diff --git a/src/f32/neon/mat4.rs b/src/f32/neon/mat4.rs
index d8a16ec..670e198 100644
--- a/src/f32/neon/mat4.rs
+++ b/src/f32/neon/mat4.rs
@@ -1126,7 +1126,7 @@ impl Mat4 {
         res = self.y_axis.mul(rhs.y).add(res);
         res = self.z_axis.mul(rhs.z).add(res);
         res = self.w_axis.add(res);
-        res = res.mul(res.wwww().recip());
+        res = res.div(res.w);
         res.xyz()
     }
 
@@ -1173,6 +1173,23 @@ impl Mat4 {
         res.xyz()
     }
 
+    /// Transforms the given [`Vec3A`] as a 3D point, applying perspective correction.
+    ///
+    /// This is the equivalent of multiplying the [`Vec3A`] as a 4D vector where `w` is `1.0`.
+    /// The perspective divide is performed meaning the resulting 3D vector is divided by `w`.
+    ///
+    /// This method assumes that `self` contains a projective transform.
+    #[inline]
+    #[must_use]
+    pub fn project_point3a(&self, rhs: Vec3A) -> Vec3A {
+        let mut res = self.x_axis.mul(rhs.xxxx());
+        res = self.y_axis.mul(rhs.yyyy()).add(res);
+        res = self.z_axis.mul(rhs.zzzz()).add(res);
+        res = self.w_axis.add(res);
+        res = res.div(res.wwww());
+        Vec3A::from_vec4(res)
+    }
+
     /// Transforms the given [`Vec3A`] as 3D point.
     ///
     /// This is the equivalent of multiplying the [`Vec3A`] as a 4D vector where `w` is `1.0`.
diff --git a/src/f32/scalar/mat4.rs b/src/f32/scalar/mat4.rs
index 490de46..5541f26 100644
--- a/src/f32/scalar/mat4.rs
+++ b/src/f32/scalar/mat4.rs
@@ -1043,7 +1043,7 @@ impl Mat4 {
         res = self.y_axis.mul(rhs.y).add(res);
         res = self.z_axis.mul(rhs.z).add(res);
         res = self.w_axis.add(res);
-        res = res.mul(res.wwww().recip());
+        res = res.div(res.w);
         res.xyz()
     }
 
@@ -1090,6 +1090,18 @@ impl Mat4 {
         res.xyz()
     }
 
+    /// Transforms the given [`Vec3A`] as a 3D point, applying perspective correction.
+    ///
+    /// This is the equivalent of multiplying the [`Vec3A`] as a 4D vector where `w` is `1.0`.
+    /// The perspective divide is performed meaning the resulting 3D vector is divided by `w`.
+    ///
+    /// This method assumes that `self` contains a projective transform.
+    #[inline]
+    #[must_use]
+    pub fn project_point3a(&self, rhs: Vec3A) -> Vec3A {
+        self.project_point3(rhs.into()).into()
+    }
+
     /// Transforms the given [`Vec3A`] as 3D point.
     ///
     /// This is the equivalent of multiplying the [`Vec3A`] as a 4D vector where `w` is `1.0`.
diff --git a/src/f32/sse2/mat4.rs b/src/f32/sse2/mat4.rs
index 88db90f..062642c 100644
--- a/src/f32/sse2/mat4.rs
+++ b/src/f32/sse2/mat4.rs
@@ -1130,7 +1130,7 @@ impl Mat4 {
         res = self.y_axis.mul(rhs.y).add(res);
         res = self.z_axis.mul(rhs.z).add(res);
         res = self.w_axis.add(res);
-        res = res.mul(res.wwww().recip());
+        res = res.div(res.w);
         res.xyz()
     }
 
@@ -1177,6 +1177,23 @@ impl Mat4 {
         res.xyz()
     }
 
+    /// Transforms the given [`Vec3A`] as a 3D point, applying perspective correction.
+    ///
+    /// This is the equivalent of multiplying the [`Vec3A`] as a 4D vector where `w` is `1.0`.
+    /// The perspective divide is performed meaning the resulting 3D vector is divided by `w`.
+    ///
+    /// This method assumes that `self` contains a projective transform.
+    #[inline]
+    #[must_use]
+    pub fn project_point3a(&self, rhs: Vec3A) -> Vec3A {
+        let mut res = self.x_axis.mul(rhs.xxxx());
+        res = self.y_axis.mul(rhs.yyyy()).add(res);
+        res = self.z_axis.mul(rhs.zzzz()).add(res);
+        res = self.w_axis.add(res);
+        res = res.div(res.wwww());
+        Vec3A::from_vec4(res)
+    }
+
     /// Transforms the given [`Vec3A`] as 3D point.
     ///
     /// This is the equivalent of multiplying the [`Vec3A`] as a 4D vector where `w` is `1.0`.
diff --git a/src/f32/wasm32/mat4.rs b/src/f32/wasm32/mat4.rs
index 43df816..46f970b 100644
--- a/src/f32/wasm32/mat4.rs
+++ b/src/f32/wasm32/mat4.rs
@@ -1121,7 +1121,7 @@ impl Mat4 {
         res = self.y_axis.mul(rhs.y).add(res);
         res = self.z_axis.mul(rhs.z).add(res);
         res = self.w_axis.add(res);
-        res = res.mul(res.wwww().recip());
+        res = res.div(res.w);
         res.xyz()
     }
 
@@ -1168,6 +1168,23 @@ impl Mat4 {
         res.xyz()
     }
 
+    /// Transforms the given [`Vec3A`] as a 3D point, applying perspective correction.
+    ///
+    /// This is the equivalent of multiplying the [`Vec3A`] as a 4D vector where `w` is `1.0`.
+    /// The perspective divide is performed meaning the resulting 3D vector is divided by `w`.
+    ///
+    /// This method assumes that `self` contains a projective transform.
+    #[inline]
+    #[must_use]
+    pub fn project_point3a(&self, rhs: Vec3A) -> Vec3A {
+        let mut res = self.x_axis.mul(rhs.xxxx());
+        res = self.y_axis.mul(rhs.yyyy()).add(res);
+        res = self.z_axis.mul(rhs.zzzz()).add(res);
+        res = self.w_axis.add(res);
+        res = res.div(res.wwww());
+        Vec3A::from_vec4(res)
+    }
+
     /// Transforms the given [`Vec3A`] as 3D point.
     ///
     /// This is the equivalent of multiplying the [`Vec3A`] as a 4D vector where `w` is `1.0`.
diff --git a/src/f64/dmat4.rs b/src/f64/dmat4.rs
index 119b316..763b826 100644
--- a/src/f64/dmat4.rs
+++ b/src/f64/dmat4.rs
@@ -1025,7 +1025,7 @@ impl DMat4 {
         res = self.y_axis.mul(rhs.y).add(res);
         res = self.z_axis.mul(rhs.z).add(res);
         res = self.w_axis.add(res);
-        res = res.mul(res.wwww().recip());
+        res = res.div(res.w);
         res.xyz()
     }
 


Test patch:
diff --git a/tests/mat4.rs b/tests/mat4.rs
index d903633..e2a760c 100644
--- a/tests/mat4.rs
+++ b/tests/mat4.rs
@@ -733,6 +733,35 @@ mod mat4 {
         );
     });
 
+    glam_test!(test_transform_vec3a, {
+        use glam::Vec3A;
+        let m = Mat4::from_axis_angle(Vec3::Z, deg(90.0));
+        let result3 = m.transform_vector3a(Vec3A::Y);
+        assert_approx_eq!(Vec3A::new(-1.0, 0.0, 0.0), result3);
+
+        let m = Mat4::from_scale_rotation_translation(
+            Vec3::new(0.5, 1.5, 2.0),
+            Quat::from_rotation_x(deg(90.0)),
+            Vec3::new(1.0, 2.0, 3.0),
+        );
+        let result3 = m.transform_vector3a(Vec3A::Y);
+        assert_approx_eq!(Vec3A::new(0.0, 0.0, 1.5), result3, 1.0e-6);
+
+        let result3 = m.transform_point3a(Vec3A::Y);
+        assert_approx_eq!(Vec3A::new(1.0, 2.0, 4.5), result3, 1.0e-6);
+
+        let m = Mat4::from_cols(
+            vec4(8.0, 0.0, 0.0, 0.0),
+            vec4(0.0, 4.0, 0.0, 0.0),
+            vec4(0.0, 0.0, 2.0, 2.0),
+            vec4(0.0, 0.0, 0.0, 0.0),
+        );
+        assert_approx_eq!(
+            Vec3A::new(4.0, 2.0, 1.0),
+            m.project_point3a(Vec3A::new(2.0, 2.0, 2.0))
+        );
+    });
+
     glam_test!(test_as, {
         use glam::DMat4;
         assert_eq!(