Repository: bitshifter/glam-rs
Pull request #385 (2023-03-13 14:11:20)

remove `serde_derive`
alternative to https://github.com/bitshifter/glam-rs/pull/384

Cuts the critical compilation chain of bevy further from `syn -> serde_derive -> glam -> bevy_math -> bevy_reflect` to `syn -> bevy_reflect_derive -> bevy_reflect` which is a few seconds faster.

**but** it makes the code less maintainable (especially if you plan to add more types with a serde derives so I can understand if you don't want to merge this. I just copied the output of `rust-analyzer`'s `Expand macro recursively` and cleaned it up a bit.

bitshifter: Seems like this doesn't work with no_std.
bitshifter: Since this was the only type using derive I'm OK with providing an implementation by hand.

It should move to `src/features/impl_serde.rs` with the rest of them though.

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 15c69e1..a5d784a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -50,7 +50,7 @@ bytemuck = { version = "1.5", optional = true, default-features = false }
 mint = { version = "0.5.8", optional = true, default-features = false }
 num-traits = { version = "0.2.14", optional = true, default-features = false }
 rand = { version = "0.8", optional = true, default-features = false }
-serde = { version = "1.0", optional = true, default-features = false, features = ["derive"] }
+serde = { version = "1.0", optional = true, default-features = false }
 rkyv = { version = "0.7", optional = true }
 bytecheck = { version = "0.6", optional = true, default-features = false}
 
diff --git a/codegen/templates/quat.rs.tera b/codegen/templates/quat.rs.tera
index b255396..1686c04 100644
--- a/codegen/templates/quat.rs.tera
+++ b/codegen/templates/quat.rs.tera
@@ -456,18 +456,18 @@ impl {{ self_t }} {
         }
     }
 
-    /// Returns the rotation axis and angle (in radians) of `self`.
+    /// Returns the rotation axis (normalized) and angle (in radians) of `self`.
     #[inline]
     pub fn to_axis_angle(self) -> ({{ vec3_t }}, {{ scalar_t }}) {
         const EPSILON: {{ scalar_t }} = 1.0e-8;
-        const EPSILON_SQUARED: {{ scalar_t }} = EPSILON * EPSILON;
-        let w = self.w;
-        let angle = w.acos_approx() * 2.0;
-        let scale_sq = {{ scalar_t }}::max(1.0 - w * w, 0.0);
-        if scale_sq >= EPSILON_SQUARED {
-            ({{ vec3_t }}::new(self.x, self.y, self.z) * scale_sq.sqrt().recip(), angle)
+        let v = {{ vec3_t }}::new(self.x, self.y, self.z);
+        let length = v.length();
+        if length >= EPSILON {
+            let angle = 2.0 * {{ scalar_t }}::atan2(length, self.w);
+            let axis = v / length;
+            (axis, angle)
         } else {
-            ({{ vec3_t }}::X, angle)
+            ({{ vec3_t }}::X, 0.0)
         }
     }
 
diff --git a/src/euler.rs b/src/euler.rs
index c97a962..cd658bf 100644
--- a/src/euler.rs
+++ b/src/euler.rs
@@ -17,7 +17,6 @@ use num_traits::Float;
 ///
 /// YXZ can be used for yaw (y-axis), pitch (x-axis), roll (z-axis).
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
 pub enum EulerRot {
     /// Intrinsic three-axis rotation ZYX
     ZYX,
diff --git a/src/f32/coresimd/quat.rs b/src/f32/coresimd/quat.rs
index 6d80256..65454fd 100644
--- a/src/f32/coresimd/quat.rs
+++ b/src/f32/coresimd/quat.rs
@@ -343,21 +343,18 @@ impl Quat {
         }
     }
 
-    /// Returns the rotation axis and angle (in radians) of `self`.
+    /// Returns the rotation axis (normalized) and angle (in radians) of `self`.
     #[inline]
     pub fn to_axis_angle(self) -> (Vec3, f32) {
         const EPSILON: f32 = 1.0e-8;
-        const EPSILON_SQUARED: f32 = EPSILON * EPSILON;
-        let w = self.w;
-        let angle = w.acos_approx() * 2.0;
-        let scale_sq = f32::max(1.0 - w * w, 0.0);
-        if scale_sq >= EPSILON_SQUARED {
-            (
-                Vec3::new(self.x, self.y, self.z) * scale_sq.sqrt().recip(),
-                angle,
-            )
+        let v = Vec3::new(self.x, self.y, self.z);
+        let length = v.length();
+        if length >= EPSILON {
+            let angle = 2.0 * f32::atan2(length, self.w);
+            let axis = v / length;
+            (axis, angle)
         } else {
-            (Vec3::X, angle)
+            (Vec3::X, 0.0)
         }
     }
 
diff --git a/src/f32/scalar/quat.rs b/src/f32/scalar/quat.rs
index 0b72e8f..3ec4f96 100644
--- a/src/f32/scalar/quat.rs
+++ b/src/f32/scalar/quat.rs
@@ -351,21 +351,18 @@ impl Quat {
         }
     }
 
-    /// Returns the rotation axis and angle (in radians) of `self`.
+    /// Returns the rotation axis (normalized) and angle (in radians) of `self`.
     #[inline]
     pub fn to_axis_angle(self) -> (Vec3, f32) {
         const EPSILON: f32 = 1.0e-8;
-        const EPSILON_SQUARED: f32 = EPSILON * EPSILON;
-        let w = self.w;
-        let angle = w.acos_approx() * 2.0;
-        let scale_sq = f32::max(1.0 - w * w, 0.0);
-        if scale_sq >= EPSILON_SQUARED {
-            (
-                Vec3::new(self.x, self.y, self.z) * scale_sq.sqrt().recip(),
-                angle,
-            )
+        let v = Vec3::new(self.x, self.y, self.z);
+        let length = v.length();
+        if length >= EPSILON {
+            let angle = 2.0 * f32::atan2(length, self.w);
+            let axis = v / length;
+            (axis, angle)
         } else {
-            (Vec3::X, angle)
+            (Vec3::X, 0.0)
         }
     }
 
diff --git a/src/f32/sse2/quat.rs b/src/f32/sse2/quat.rs
index 47ad8d7..12fdf84 100644
--- a/src/f32/sse2/quat.rs
+++ b/src/f32/sse2/quat.rs
@@ -350,21 +350,18 @@ impl Quat {
         }
     }
 
-    /// Returns the rotation axis and angle (in radians) of `self`.
+    /// Returns the rotation axis (normalized) and angle (in radians) of `self`.
     #[inline]
     pub fn to_axis_angle(self) -> (Vec3, f32) {
         const EPSILON: f32 = 1.0e-8;
-        const EPSILON_SQUARED: f32 = EPSILON * EPSILON;
-        let w = self.w;
-        let angle = w.acos_approx() * 2.0;
-        let scale_sq = f32::max(1.0 - w * w, 0.0);
-        if scale_sq >= EPSILON_SQUARED {
-            (
-                Vec3::new(self.x, self.y, self.z) * scale_sq.sqrt().recip(),
-                angle,
-            )
+        let v = Vec3::new(self.x, self.y, self.z);
+        let length = v.length();
+        if length >= EPSILON {
+            let angle = 2.0 * f32::atan2(length, self.w);
+            let axis = v / length;
+            (axis, angle)
         } else {
-            (Vec3::X, angle)
+            (Vec3::X, 0.0)
         }
     }
 
diff --git a/src/f32/wasm32/quat.rs b/src/f32/wasm32/quat.rs
index 798faee..e5eed2e 100644
--- a/src/f32/wasm32/quat.rs
+++ b/src/f32/wasm32/quat.rs
@@ -343,21 +343,18 @@ impl Quat {
         }
     }
 
-    /// Returns the rotation axis and angle (in radians) of `self`.
+    /// Returns the rotation axis (normalized) and angle (in radians) of `self`.
     #[inline]
     pub fn to_axis_angle(self) -> (Vec3, f32) {
         const EPSILON: f32 = 1.0e-8;
-        const EPSILON_SQUARED: f32 = EPSILON * EPSILON;
-        let w = self.w;
-        let angle = w.acos_approx() * 2.0;
-        let scale_sq = f32::max(1.0 - w * w, 0.0);
-        if scale_sq >= EPSILON_SQUARED {
-            (
-                Vec3::new(self.x, self.y, self.z) * scale_sq.sqrt().recip(),
-                angle,
-            )
+        let v = Vec3::new(self.x, self.y, self.z);
+        let length = v.length();
+        if length >= EPSILON {
+            let angle = 2.0 * f32::atan2(length, self.w);
+            let axis = v / length;
+            (axis, angle)
         } else {
-            (Vec3::X, angle)
+            (Vec3::X, 0.0)
         }
     }
 
diff --git a/src/f64/dquat.rs b/src/f64/dquat.rs
index 6f89430..66a175c 100644
--- a/src/f64/dquat.rs
+++ b/src/f64/dquat.rs
@@ -341,21 +341,18 @@ impl DQuat {
         }
     }
 
-    /// Returns the rotation axis and angle (in radians) of `self`.
+    /// Returns the rotation axis (normalized) and angle (in radians) of `self`.
     #[inline]
     pub fn to_axis_angle(self) -> (DVec3, f64) {
         const EPSILON: f64 = 1.0e-8;
-        const EPSILON_SQUARED: f64 = EPSILON * EPSILON;
-        let w = self.w;
-        let angle = w.acos_approx() * 2.0;
-        let scale_sq = f64::max(1.0 - w * w, 0.0);
-        if scale_sq >= EPSILON_SQUARED {
-            (
-                DVec3::new(self.x, self.y, self.z) * scale_sq.sqrt().recip(),
-                angle,
-            )
+        let v = DVec3::new(self.x, self.y, self.z);
+        let length = v.length();
+        if length >= EPSILON {
+            let angle = 2.0 * f64::atan2(length, self.w);
+            let axis = v / length;
+            (axis, angle)
         } else {
-            (DVec3::X, angle)
+            (DVec3::X, 0.0)
         }
     }
 
diff --git a/src/features/impl_serde.rs b/src/features/impl_serde.rs
index c3a44e0..547240f 100644
--- a/src/features/impl_serde.rs
+++ b/src/features/impl_serde.rs
@@ -1000,3 +1000,181 @@ mod u32 {
 
     impl_serde_vec_types!(u32, UVec2, UVec3, UVec4);
 }
+
+mod euler {
+    use crate::EulerRot;
+
+    impl serde::Serialize for EulerRot {
+        fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+            match *self {
+                EulerRot::ZYX => {
+                    serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 0u32, "ZYX")
+                }
+                EulerRot::ZXY => {
+                    serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 1u32, "ZXY")
+                }
+                EulerRot::YXZ => {
+                    serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 2u32, "YXZ")
+                }
+                EulerRot::YZX => {
+                    serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 3u32, "YZX")
+                }
+                EulerRot::XYZ => {
+                    serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 4u32, "XYZ")
+                }
+                EulerRot::XZY => {
+                    serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 5u32, "XZY")
+                }
+            }
+        }
+    }
+
+    impl<'de> serde::Deserialize<'de> for EulerRot {
+        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+        where
+            D: serde::Deserializer<'de>,
+        {
+            #[allow(clippy::upper_case_acronyms)]
+            enum Field {
+                ZYX,
+                ZXY,
+                YXZ,
+                YZX,
+                XYZ,
+                XZY,
+            }
+            struct FieldVisitor;
+
+            impl<'de> serde::de::Visitor<'de> for FieldVisitor {
+                type Value = Field;
+                fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+                    core::fmt::Formatter::write_str(formatter, "variant identifier")
+                }
+                fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+                where
+                    E: serde::de::Error,
+                {
+                    match value {
+                        0u64 => Ok(Field::ZYX),
+                        1u64 => Ok(Field::ZXY),
+                        2u64 => Ok(Field::YXZ),
+                        3u64 => Ok(Field::YZX),
+                        4u64 => Ok(Field::XYZ),
+                        5u64 => Ok(Field::XZY),
+                        _ => Err(serde::de::Error::invalid_value(
+                            serde::de::Unexpected::Unsigned(value),
+                            &"variant index 0 <= i < 6",
+                        )),
+                    }
+                }
+                fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
+                where
+                    E: serde::de::Error,
+                {
+                    match value {
+                        "ZYX" => Ok(Field::ZYX),
+                        "ZXY" => Ok(Field::ZXY),
+                        "YXZ" => Ok(Field::YXZ),
+                        "YZX" => Ok(Field::YZX),
+                        "XYZ" => Ok(Field::XYZ),
+                        "XZY" => Ok(Field::XZY),
+                        _ => Err(serde::de::Error::unknown_variant(value, VARIANTS)),
+                    }
+                }
+                fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
+                where
+                    E: serde::de::Error,
+                {
+                    match value {
+                        b"ZYX" => Ok(Field::ZYX),
+                        b"ZXY" => Ok(Field::ZXY),
+                        b"YXZ" => Ok(Field::YXZ),
+                        b"YZX" => Ok(Field::YZX),
+                        b"XYZ" => Ok(Field::XYZ),
+                        b"XZY" => Ok(Field::XZY),
+                        _ => {
+                            #[cfg(feature = "std")]
+                            let value = &String::from_utf8_lossy(value);
+                            #[cfg(not(feature = "std"))]
+                            let value =
+                                core::str::from_utf8(value).unwrap_or("\u{fffd}\u{fffd}\u{fffd}");
+                            Err(serde::de::Error::unknown_variant(value, VARIANTS))
+                        }
+                    }
+                }
+            }
+            impl<'de> serde::Deserialize<'de> for Field {
+                #[inline]
+                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+                where
+                    D: serde::Deserializer<'de>,
+                {
+                    serde::Deserializer::deserialize_identifier(deserializer, FieldVisitor)
+                }
+            }
+            struct Visitor<'de> {
+                marker: core::marker::PhantomData<EulerRot>,
+                lifetime: core::marker::PhantomData<&'de ()>,
+            }
+            impl<'de> serde::de::Visitor<'de> for Visitor<'de> {
+                type Value = EulerRot;
+                fn expecting(
+                    &self,
+                    __formatter: &mut core::fmt::Formatter<'_>,
+                ) -> core::fmt::Result {
+                    core::fmt::Formatter::write_str(__formatter, "enum EulerRot")
+                }
+                fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
+                where
+                    A: serde::de::EnumAccess<'de>,
+                {
+                    match serde::de::EnumAccess::variant(data)? {
+                        (Field::ZYX, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::ZYX)
+                        }
+                        (Field::ZXY, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::ZXY)
+                        }
+                        (Field::YXZ, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::YXZ)
+                        }
+                        (Field::YZX, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::YZX)
+                        }
+                        (Field::XYZ, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::XYZ)
+                        }
+                        (Field::XZY, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::XZY)
+                        }
+                    }
+                }
+            }
+            const VARIANTS: &[&str] = &["ZYX", "ZXY", "YXZ", "YZX", "XYZ", "XZY"];
+            serde::Deserializer::deserialize_enum(
+                deserializer,
+                "EulerRot",
+                VARIANTS,
+                Visitor {
+                    marker: core::marker::PhantomData::<EulerRot>,
+                    lifetime: core::marker::PhantomData,
+                },
+            )
+        }
+    }
+
+    #[test]
+    fn test_euler_rot_serde() {
+        let a = EulerRot::XYZ;
+        let serialized = serde_json::to_string(&a).unwrap();
+        assert_eq!("\"XYZ\"", serialized);
+        let deserialized = serde_json::from_str(&serialized).unwrap();
+        assert_eq!(a, deserialized);
+    }
+}


Test patch:
diff --git a/tests/quat.rs b/tests/quat.rs
index 1a6f6e3..499ff40 100644
--- a/tests/quat.rs
+++ b/tests/quat.rs
@@ -507,6 +507,47 @@ macro_rules! impl_quat_tests {
         glam_test!(test_to_array, {
             assert!($new(1.0, 2.0, 3.0, 4.0).to_array() == [1.0, 2.0, 3.0, 4.0]);
         });
+
+        glam_test!(test_to_axis_angle, {
+            {
+                let q = $quat::from_xyzw(
+                    5.28124762e-08,
+                    -5.12559303e-03,
+                    8.29266140e-08,
+                    9.99986828e-01,
+                );
+                assert!(q.is_normalized());
+                let (axis, angle) = q.to_axis_angle();
+                assert!(axis.is_normalized());
+                let q2 = $quat::from_axis_angle(axis, angle);
+                assert!((q.dot(q2) - 1.0).abs() < 1e-6);
+            }
+            {
+                let q = $quat::IDENTITY;
+                let (axis, angle) = q.to_axis_angle();
+                assert!(axis.is_normalized());
+                let q2 = $quat::from_axis_angle(axis, angle);
+                assert!((q.dot(q2) - 1.0).abs() < 1e-6);
+            }
+            {
+                let q = $quat::from_xyzw(0.0, 1.0, 0.0, 0.0);
+                assert!(q.is_normalized());
+                let (axis, angle) = q.to_axis_angle();
+                assert!(axis.is_normalized());
+                let q2 = $quat::from_axis_angle(axis, angle);
+                assert!((q.dot(q2) - 1.0).abs() < 1e-6);
+            }
+            {
+                let axis = $vec3::Z;
+                let angle = core::$t::consts::PI * 0.25;
+                let q = $quat::from_axis_angle(axis, angle);
+                assert!(q.is_normalized());
+                let (axis2, angle2) = q.to_axis_angle();
+                assert!(axis.is_normalized());
+                assert_approx_eq!(axis, axis2);
+                assert_approx_eq!(angle, angle2);
+            }
+        });
     };
 }