Repository: bitshifter/glam-rs
Pull request #534 (2024-07-05 13:30:20)

Make random quaternions uniformly random (via `Standard`)
Previously, randomly sampled `Quat`s had bias because they were constructed using Euler rotation sequences. This PR makes them follow a uniform distribution. The algorithm used is the one described [here](https://web.archive.org/web/20180724074257/http://planning.cs.uiuc.edu/node198.html). 

This is essentially a follow-up from [this bevy_math PR](https://github.com/bevyengine/bevy/pull/12857), which affords users the ability to ergonomically obtain random directions and quaternions.

I believe that this implementation also conforms more closely with the philosophy of [Standard](https://docs.rs/rand/latest/rand/distributions/struct.Standard.html): since the space of quaternions is compact, it is quite natural for the "default" distribution over them to be uniform. 

Furthermore, if a user wants the old distribution, it is easily implemented using just `Rng::gen_range` and `Quat`'s constructors; the present implementation actually bundles relatively specialized knowledge into an interface, which abstracts difficulties away from users.

NthTensor: Over at bevy we've had several users be confused when they encounter the current nonuniform version. We can't really fix this in our side, and to me this seems like a better default.
bitshifter: You need to use `math::sin_cos` which wraps libm and std implementations.
bitshifter: I don't have a strong feeling about performance characteristics of this. It's something I added at the very beginning of glam to generate some data to run benchmarks on, it's not something I put any thought into at the time. It seems like there's still a bit of discussion happening on bevy discord, let me know what you settle on.

Patch:
diff --git a/codegen/templates/mat.rs.tera b/codegen/templates/mat.rs.tera
index c6045f4..67b0700 100644
--- a/codegen/templates/mat.rs.tera
+++ b/codegen/templates/mat.rs.tera
@@ -72,7 +72,7 @@ use crate::{
         Mat3A,
     {% endif %}
 {% elif dim == 3 %}
-    EulerRot,
+    EulerRot, euler::{FromEuler, ToEuler},
     {{ mat2_t }}, {{ mat4_t }}, {{ quat_t }}, {{ vec2_t }}, {{ col_t }},
     {% if is_align %}
         {{ mat3_t }}, {{ vec3_t }},
@@ -80,7 +80,7 @@ use crate::{
         Mat3A, Vec3A,
     {% endif %}
 {% elif dim == 4 %}
-    EulerRot,
+    EulerRot, euler::{FromEuler, ToEuler},
     {{ mat3_t }}, {{ quat_t }}, {{ vec3_t }}, {{ col_t }},
     {% if scalar_t == "f32" %}
         Mat3A, Vec3A,
@@ -521,8 +521,24 @@ impl {{ self_t }} {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: {{ scalar_t }}, b: {{ scalar_t }}, c: {{ scalar_t }}) -> Self {
-        let quat = {{ quat_t }}::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the input matrix contains scales, shears, or other non-rotation transformations then
+    /// the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> ({{ scalar_t }}, {{ scalar_t }}, {{ scalar_t }}) {
+        glam_assert!(self.x_axis.is_normalized()
+            && self.y_axis.is_normalized()
+            && self.z_axis.is_normalized());
+        self.to_euler_angles(order)
     }
 
     /// Creates a 3D rotation matrix from `angle` (in radians) around the x axis.
@@ -853,8 +869,25 @@ impl {{ self_t }} {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: {{ scalar_t }}, b: {{ scalar_t }}, c: {{ scalar_t }}) -> Self {
-        let quat = {{ quat_t }}::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> ({{ scalar_t }}, {{ scalar_t }}, {{ scalar_t }}) {
+        glam_assert!(self.x_axis.xyz().is_normalized()
+            && self.y_axis.xyz().is_normalized()
+            && self.z_axis.xyz().is_normalized());
+        self.to_euler_angles(order)
     }
 
     /// Creates an affine transformation matrix containing a 3D rotation around the x axis of
diff --git a/codegen/templates/quat.rs.tera b/codegen/templates/quat.rs.tera
index a9495df..cf8f7b1 100644
--- a/codegen/templates/quat.rs.tera
+++ b/codegen/templates/quat.rs.tera
@@ -33,7 +33,7 @@
 
 use crate::{
     {{ scalar_t }}::math,
-    euler::{EulerFromQuaternion, EulerRot, EulerToQuaternion},
+    euler::{EulerRot, FromEuler, ToEuler},
     {% if scalar_t == "f32" %}
         DQuat, Mat3, Mat3A, Mat4, Vec2, Vec3, Vec3A, Vec4,
     {% elif scalar_t == "f64" %}
@@ -294,14 +294,22 @@ impl {{ self_t }} {
     #[inline]
     #[must_use]
     pub fn from_euler(euler: EulerRot, a: {{ scalar_t }}, b: {{ scalar_t }}, c: {{ scalar_t }}) -> Self {
-        euler.new_quat(a, b, c)
+        Self::from_euler_angles(euler, a, b, c)
     }
 
     /// From the columns of a 3x3 rotation matrix.
+    ///
+    /// Note if the input axes contain scales, shears, or other non-rotation transformations then
+    /// the output of this function is ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any axis is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub(crate) fn from_rotation_axes(x_axis: {{ vec3_t }}, y_axis: {{ vec3_t }}, z_axis: {{ vec3_t }}) -> Self {
-        // Based on https://github.com/microsoft/DirectXMath `XM$quaternionRotationMatrix`
+        glam_assert!(x_axis.is_normalized() && y_axis.is_normalized() && z_axis.is_normalized());
+        // Based on https://github.com/microsoft/DirectXMath `XMQuaternionRotationMatrix`
         let (m00, m01, m02) = x_axis.into();
         let (m10, m11, m12) = y_axis.into();
         let (m20, m21, m22) = z_axis.into();
@@ -359,6 +367,13 @@ impl {{ self_t }} {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3(mat: &{{ mat3_t }}) -> Self {
@@ -371,6 +386,13 @@ impl {{ self_t }} {
 
 {% if scalar_t == "f32" %}
     /// Creates a quaternion from a 3x3 SIMD aligned rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3a(mat: &Mat3A) -> Self {
@@ -382,7 +404,15 @@ impl {{ self_t }} {
     }
 {% endif %}
 
-    /// Creates a quaternion from a 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    /// Creates a quaternion from the upper 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat4(mat: &{{ mat4_t }}) -> Self {
@@ -515,8 +545,8 @@ impl {{ self_t }} {
     /// Returns the rotation angles for the given euler rotation sequence.
     #[inline]
     #[must_use]
-    pub fn to_euler(self, euler: EulerRot) -> ({{ scalar_t }}, {{ scalar_t }}, {{ scalar_t }}) {
-        euler.convert_quat(self)
+    pub fn to_euler(self, order: EulerRot) -> ({{ scalar_t }}, {{ scalar_t }}, {{ scalar_t }}) {
+        self.to_euler_angles(order)
     }
 
     /// `[x, y, z, w]`
@@ -1113,6 +1143,14 @@ impl {{ self_t }} {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix inside a 3D affine transform.
+    ///
+    /// Note if the input affine matrix contain scales, shears, or other non-rotation
+    /// transformations then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input affine matrix column is not normalized when `glam_assert` is
+    /// enabled.
     #[inline]
     #[must_use]
     pub fn from_affine3(a: &crate::{{ affine3_t }}) -> Self {
diff --git a/codegen/templates/vec_mask.rs.tera b/codegen/templates/vec_mask.rs.tera
index 41e0e16..53f7135 100644
--- a/codegen/templates/vec_mask.rs.tera
+++ b/codegen/templates/vec_mask.rs.tera
@@ -51,7 +51,18 @@ union UnionCast {
 }
 {% endif %}
 
-{%- if is_scalar and is_bool %}
+/// Creates a {{ dim }}-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn {{ self_t | lower }}(
+    {% for c in components %}
+        {{ c }}: bool,
+    {% endfor %}
+) -> {{ self_t }} {
+    {{ self_t }}::new({{ components | join(sep=",") }})
+}
+
+{% if is_scalar and is_bool %}
 /// A {{ dim }}-dimensional `bool` vector mask.
 {%- elif is_scalar and is_u32 %}
 /// A {{ dim }}-dimensional `u32` vector mask.
diff --git a/src/bool.rs b/src/bool.rs
index 091cf0c..c920e0b 100644
--- a/src/bool.rs
+++ b/src/bool.rs
@@ -34,47 +34,47 @@ mod wasm32;
 ))]
 mod scalar;
 
-pub use bvec2::BVec2;
-pub use bvec3::BVec3;
-pub use bvec4::BVec4;
+pub use bvec2::{bvec2, BVec2};
+pub use bvec3::{bvec3, BVec3};
+pub use bvec4::{bvec4, BVec4};
 
 #[cfg(all(
     target_arch = "aarch64",
     not(any(feature = "core-simd", feature = "scalar-math"))
 ))]
-pub use neon::bvec3a::BVec3A;
+pub use neon::bvec3a::{bvec3a, BVec3A};
 #[cfg(all(
     target_arch = "aarch64",
     not(any(feature = "core-simd", feature = "scalar-math"))
 ))]
-pub use neon::bvec4a::BVec4A;
+pub use neon::bvec4a::{bvec4a, BVec4A};
 
 #[cfg(all(
     target_feature = "sse2",
     not(any(feature = "core-simd", feature = "scalar-math"))
 ))]
-pub use sse2::bvec3a::BVec3A;
+pub use sse2::bvec3a::{bvec3a, BVec3A};
 #[cfg(all(
     target_feature = "sse2",
     not(any(feature = "core-simd", feature = "scalar-math"))
 ))]
-pub use sse2::bvec4a::BVec4A;
+pub use sse2::bvec4a::{bvec4a, BVec4A};
 
 #[cfg(all(
     target_feature = "simd128",
     not(any(feature = "core-simd", feature = "scalar-math"))
 ))]
-pub use wasm32::bvec3a::BVec3A;
+pub use wasm32::bvec3a::{bvec3a, BVec3A};
 #[cfg(all(
     target_feature = "simd128",
     not(any(feature = "core-simd", feature = "scalar-math"))
 ))]
-pub use wasm32::bvec4a::BVec4A;
+pub use wasm32::bvec4a::{bvec4a, BVec4A};
 
 #[cfg(all(feature = "core-simd", not(feature = "scalar-math")))]
-pub use coresimd::bvec3a::BVec3A;
+pub use coresimd::bvec3a::{bvec3a, BVec3A};
 #[cfg(all(feature = "core-simd", not(feature = "scalar-math")))]
-pub use coresimd::bvec4a::BVec4A;
+pub use coresimd::bvec4a::{bvec4a, BVec4A};
 
 #[cfg(any(
     not(any(
@@ -85,16 +85,18 @@ pub use coresimd::bvec4a::BVec4A;
     )),
     feature = "scalar-math"
 ))]
-pub use scalar::bvec3a::BVec3A;
+pub use scalar::bvec3a::{bvec3a, BVec3A};
 
-#[cfg(not(any(
-    feature = "scalar-math",
-    feature = "core-simd",
-    target_arch = "aarch64",
-    target_feature = "sse2",
-    target_feature = "simd128"
-),))]
-pub use scalar::bvec4a::BVec4A;
+#[cfg(any(
+    not(any(
+        feature = "core-simd",
+        target_arch = "aarch64",
+        target_feature = "sse2",
+        target_feature = "simd128"
+    )),
+    feature = "scalar-math"
+))]
+pub use scalar::bvec4a::{bvec4a, BVec4A};
 
 mod const_test_bvec2 {
     const_assert_eq!(1, core::mem::align_of::<super::BVec2>());
diff --git a/src/bool/bvec2.rs b/src/bool/bvec2.rs
index 3fc13b1..5ac62cb 100644
--- a/src/bool/bvec2.rs
+++ b/src/bool/bvec2.rs
@@ -4,6 +4,13 @@
 use core::fmt;
 use core::ops::*;
 
+/// Creates a 2-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec2(x: bool, y: bool) -> BVec2 {
+    BVec2::new(x, y)
+}
+
 /// A 2-dimensional `bool` vector mask.
 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
 #[repr(C, align(1))]
diff --git a/src/bool/bvec3.rs b/src/bool/bvec3.rs
index 2f33cd1..94fd2b9 100644
--- a/src/bool/bvec3.rs
+++ b/src/bool/bvec3.rs
@@ -4,6 +4,13 @@
 use core::fmt;
 use core::ops::*;
 
+/// Creates a 3-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec3(x: bool, y: bool, z: bool) -> BVec3 {
+    BVec3::new(x, y, z)
+}
+
 /// A 3-dimensional `bool` vector mask.
 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
 #[repr(C, align(1))]
diff --git a/src/bool/bvec4.rs b/src/bool/bvec4.rs
index 2300a4a..c1f4931 100644
--- a/src/bool/bvec4.rs
+++ b/src/bool/bvec4.rs
@@ -4,6 +4,13 @@
 use core::fmt;
 use core::ops::*;
 
+/// Creates a 4-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec4(x: bool, y: bool, z: bool, w: bool) -> BVec4 {
+    BVec4::new(x, y, z, w)
+}
+
 /// A 4-dimensional `bool` vector mask.
 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
 #[repr(C, align(1))]
diff --git a/src/bool/coresimd/bvec3a.rs b/src/bool/coresimd/bvec3a.rs
index 080f3bd..4615fb4 100644
--- a/src/bool/coresimd/bvec3a.rs
+++ b/src/bool/coresimd/bvec3a.rs
@@ -12,6 +12,13 @@ union UnionCast {
     v: BVec3A,
 }
 
+/// Creates a 3-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec3a(x: bool, y: bool, z: bool) -> BVec3A {
+    BVec3A::new(x, y, z)
+}
+
 /// A 3-dimensional SIMD vector mask.
 ///
 /// This type is 16 byte aligned.
diff --git a/src/bool/coresimd/bvec4a.rs b/src/bool/coresimd/bvec4a.rs
index a1213b0..dc65730 100644
--- a/src/bool/coresimd/bvec4a.rs
+++ b/src/bool/coresimd/bvec4a.rs
@@ -12,6 +12,13 @@ union UnionCast {
     v: BVec4A,
 }
 
+/// Creates a 4-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec4a(x: bool, y: bool, z: bool, w: bool) -> BVec4A {
+    BVec4A::new(x, y, z, w)
+}
+
 /// A 4-dimensional SIMD vector mask.
 ///
 /// This type is 16 byte aligned.
diff --git a/src/bool/neon/bvec3a.rs b/src/bool/neon/bvec3a.rs
index 3e26c2c..7672a18 100644
--- a/src/bool/neon/bvec3a.rs
+++ b/src/bool/neon/bvec3a.rs
@@ -12,6 +12,13 @@ union UnionCast {
     v: BVec3A,
 }
 
+/// Creates a 3-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec3a(x: bool, y: bool, z: bool) -> BVec3A {
+    BVec3A::new(x, y, z)
+}
+
 /// A 3-dimensional SIMD vector mask.
 ///
 /// This type is 16 byte aligned.
diff --git a/src/bool/neon/bvec4a.rs b/src/bool/neon/bvec4a.rs
index 27a188f..c8a1700 100644
--- a/src/bool/neon/bvec4a.rs
+++ b/src/bool/neon/bvec4a.rs
@@ -12,6 +12,13 @@ union UnionCast {
     v: BVec4A,
 }
 
+/// Creates a 4-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec4a(x: bool, y: bool, z: bool, w: bool) -> BVec4A {
+    BVec4A::new(x, y, z, w)
+}
+
 /// A 4-dimensional SIMD vector mask.
 ///
 /// This type is 16 byte aligned.
diff --git a/src/bool/scalar/bvec3a.rs b/src/bool/scalar/bvec3a.rs
index 28c9f45..bd687af 100644
--- a/src/bool/scalar/bvec3a.rs
+++ b/src/bool/scalar/bvec3a.rs
@@ -4,6 +4,13 @@
 use core::fmt;
 use core::ops::*;
 
+/// Creates a 3-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec3a(x: bool, y: bool, z: bool) -> BVec3A {
+    BVec3A::new(x, y, z)
+}
+
 /// A 3-dimensional `u32` vector mask.
 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
 #[repr(C, align(16))]
diff --git a/src/bool/scalar/bvec4a.rs b/src/bool/scalar/bvec4a.rs
index eaf6572..82b4640 100644
--- a/src/bool/scalar/bvec4a.rs
+++ b/src/bool/scalar/bvec4a.rs
@@ -4,6 +4,13 @@
 use core::fmt;
 use core::ops::*;
 
+/// Creates a 4-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec4a(x: bool, y: bool, z: bool, w: bool) -> BVec4A {
+    BVec4A::new(x, y, z, w)
+}
+
 /// A 4-dimensional `u32` vector mask.
 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
 #[repr(C, align(16))]
diff --git a/src/bool/sse2/bvec3a.rs b/src/bool/sse2/bvec3a.rs
index 89b36c3..5c1943d 100644
--- a/src/bool/sse2/bvec3a.rs
+++ b/src/bool/sse2/bvec3a.rs
@@ -15,6 +15,13 @@ union UnionCast {
     v: BVec3A,
 }
 
+/// Creates a 3-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec3a(x: bool, y: bool, z: bool) -> BVec3A {
+    BVec3A::new(x, y, z)
+}
+
 /// A 3-dimensional SIMD vector mask.
 ///
 /// This type is 16 byte aligned.
diff --git a/src/bool/sse2/bvec4a.rs b/src/bool/sse2/bvec4a.rs
index 344892d..9ea076d 100644
--- a/src/bool/sse2/bvec4a.rs
+++ b/src/bool/sse2/bvec4a.rs
@@ -15,6 +15,13 @@ union UnionCast {
     v: BVec4A,
 }
 
+/// Creates a 4-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec4a(x: bool, y: bool, z: bool, w: bool) -> BVec4A {
+    BVec4A::new(x, y, z, w)
+}
+
 /// A 4-dimensional SIMD vector mask.
 ///
 /// This type is 16 byte aligned.
diff --git a/src/bool/wasm32/bvec3a.rs b/src/bool/wasm32/bvec3a.rs
index 1930290..5e889a2 100644
--- a/src/bool/wasm32/bvec3a.rs
+++ b/src/bool/wasm32/bvec3a.rs
@@ -6,6 +6,13 @@ use core::ops::*;
 
 use core::arch::wasm32::*;
 
+/// Creates a 3-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec3a(x: bool, y: bool, z: bool) -> BVec3A {
+    BVec3A::new(x, y, z)
+}
+
 /// A 3-dimensional SIMD vector mask.
 ///
 /// This type is 16 byte aligned.
diff --git a/src/bool/wasm32/bvec4a.rs b/src/bool/wasm32/bvec4a.rs
index 4bb5afa..65438a1 100644
--- a/src/bool/wasm32/bvec4a.rs
+++ b/src/bool/wasm32/bvec4a.rs
@@ -6,6 +6,13 @@ use core::ops::*;
 
 use core::arch::wasm32::*;
 
+/// Creates a 4-dimensional `bool` vector mask.
+#[inline(always)]
+#[must_use]
+pub const fn bvec4a(x: bool, y: bool, z: bool, w: bool) -> BVec4A {
+    BVec4A::new(x, y, z, w)
+}
+
 /// A 4-dimensional SIMD vector mask.
 ///
 /// This type is 16 byte aligned.
diff --git a/src/euler.rs b/src/euler.rs
index e180046..7a90d40 100644
--- a/src/euler.rs
+++ b/src/euler.rs
@@ -1,17 +1,28 @@
-/*
-Conversion from quaternions to Euler rotation sequences.
-
-From: http://bediyap.com/programming/convert-quaternion-to-euler-rotations/
-*/
-
-use crate::{DQuat, Quat};
+// Based on Ken Shoemake. 1994. Euler angle conversion. Graphics gems IV.  Academic Press
+// Professional, Inc., USA, 222–229.
+use crate::{DMat3, DMat4, DQuat, DVec3, Mat3, Mat3A, Mat4, Quat, Vec3, Vec3A, Vec3Swizzles};
 
 /// Euler rotation sequences.
 ///
-/// The angles are applied starting from the right.
-/// E.g. XYZ will first apply the z-axis rotation.
+/// The three elemental rotations may be extrinsic (rotations about the axes xyz of the original
+/// coordinate system, which is assumed to remain motionless), or intrinsic(rotations about the
+/// axes of the rotating coordinate system XYZ, solidary with the moving body, which changes its
+/// orientation after each elemental rotation).
+///
+/// ```
+/// # use glam::{EulerRot, Mat3};
+/// # let i = core::f32::consts::FRAC_PI_2;
+/// # let j = core::f32::consts::FRAC_PI_4;
+/// # let k = core::f32::consts::FRAC_PI_8;
+/// let m_intrinsic = Mat3::from_rotation_x(i) * Mat3::from_rotation_y(j) * Mat3::from_rotation_z(k);
+/// let n_intrinsic = Mat3::from_euler(EulerRot::XYZ, i, j, k);
+/// assert!(m_intrinsic.abs_diff_eq(n_intrinsic, 2e-6));
+///
+/// let m_extrinsic = Mat3::from_rotation_z(k) * Mat3::from_rotation_y(j) * Mat3::from_rotation_x(i);
+/// let n_extrinsic = Mat3::from_euler(EulerRot::XYZEx, i, j, k);
+/// assert!(m_extrinsic.abs_diff_eq(n_extrinsic, 2e-6));
+/// ```
 ///
-/// YXZ can be used for yaw (y-axis), pitch (x-axis), roll (z-axis).
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum EulerRot {
     /// Intrinsic three-axis rotation ZYX
@@ -26,167 +37,401 @@ pub enum EulerRot {
     XYZ,
     /// Intrinsic three-axis rotation XZY
     XZY,
+
+    /// Intrinsic two-axis rotation ZYZ
+    ZYZ,
+    /// Intrinsic two-axis rotation ZXZ
+    ZXZ,
+    /// Intrinsic two-axis rotation YXY
+    YXY,
+    /// Intrinsic two-axis rotation YZY
+    YZY,
+    /// Intrinsic two-axis rotation XYX
+    XYX,
+    /// Intrinsic two-axis rotation XZX
+    XZX,
+
+    /// Extrinsic three-axis rotation ZYX
+    ZYXEx,
+    /// Extrinsic three-axis rotation ZXY
+    ZXYEx,
+    /// Extrinsic three-axis rotation YXZ
+    YXZEx,
+    /// Extrinsic three-axis rotation YZX
+    YZXEx,
+    /// Extrinsic three-axis rotation XYZ
+    XYZEx,
+    /// Extrinsic three-axis rotation XZY
+    XZYEx,
+
+    /// Extrinsic two-axis rotation ZYZ
+    ZYZEx,
+    /// Extrinsic two-axis rotation ZXZ
+    ZXZEx,
+    /// Extrinsic two-axis rotation YXY
+    YXYEx,
+    /// Extrinsic two-axis rotation YZY
+    YZYEx,
+    /// Extrinsic two-axis rotation XYX
+    XYXEx,
+    /// Extrinsic two-axis rotation XZX
+    XZXEx,
 }
 
-impl Default for EulerRot {
-    /// Default `YXZ` as yaw (y-axis), pitch (x-axis), roll (z-axis).
-    fn default() -> Self {
-        Self::YXZ
-    }
+pub(crate) trait ToEuler {
+    type Scalar;
+    fn to_euler_angles(self, order: EulerRot) -> (Self::Scalar, Self::Scalar, Self::Scalar);
+}
+
+pub(crate) trait FromEuler {
+    type Scalar;
+    fn from_euler_angles(
+        order: EulerRot,
+        i: Self::Scalar,
+        j: Self::Scalar,
+        k: Self::Scalar,
+    ) -> Self;
 }
 
-/// Conversion from quaternion to euler angles.
-pub(crate) trait EulerFromQuaternion<Q: Copy>: Sized + Copy {
-    type Output;
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum Axis {
+    X = 0,
+    Y = 1,
+    Z = 2,
+}
 
-    /// Compute all angles of a rotation in the notation order
-    fn convert_quat(self, q: Q) -> (Self::Output, Self::Output, Self::Output);
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum Parity {
+    Odd = 0,
+    Even = 1,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum Repeated {
+    No = 0,
+    Yes = 1,
+}
 
-    #[doc(hidden)]
-    fn sine_theta(self, q: Q) -> Self::Output;
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum Frame {
+    Relative = 0,
+    Static = 1,
 }
 
-/// Conversion from euler angles to quaternion.
-pub(crate) trait EulerToQuaternion<T>: Copy {
-    type Output;
-    /// Create the rotation quaternion for the three angles of this euler rotation sequence.
-    fn new_quat(self, u: T, v: T, w: T) -> Self::Output;
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+struct Order {
+    initial_axis: Axis,
+    parity_even: bool,
+    initial_repeated: bool,
+    frame_static: bool,
 }
 
-macro_rules! impl_from_quat {
-    ($t:ident, $quat:ident) => {
-        impl EulerFromQuaternion<$quat> for EulerRot {
-            type Output = $t;
-
-            fn sine_theta(self, q: $quat) -> $t {
-                use EulerRot::*;
-                match self {
-                    ZYX => -2.0 * (q.x * q.z - q.w * q.y),
-                    ZXY => 2.0 * (q.y * q.z + q.w * q.x),
-                    YXZ => -2.0 * (q.y * q.z - q.w * q.x),
-                    YZX => 2.0 * (q.x * q.y + q.w * q.z),
-                    XYZ => 2.0 * (q.x * q.z + q.w * q.y),
-                    XZY => -2.0 * (q.x * q.y - q.w * q.z),
+impl Order {
+    const fn new(
+        initial_axis: Axis,
+        parity: Parity,
+        initial_repeated: Repeated,
+        frame: Frame,
+    ) -> Self {
+        Self {
+            initial_axis,
+            parity_even: parity as u32 == Parity::Even as u32,
+            initial_repeated: initial_repeated as u32 == Repeated::Yes as u32,
+            frame_static: frame as u32 == Frame::Static as u32,
+        }
+    }
+
+    const fn from_euler(euler: EulerRot) -> Self {
+        match euler {
+            EulerRot::XYZ => Self::new(Axis::X, Parity::Even, Repeated::No, Frame::Static),
+            EulerRot::XYX => Self::new(Axis::X, Parity::Even, Repeated::Yes, Frame::Static),
+            EulerRot::XZY => Self::new(Axis::X, Parity::Odd, Repeated::No, Frame::Static),
+            EulerRot::XZX => Self::new(Axis::X, Parity::Odd, Repeated::Yes, Frame::Static),
+            EulerRot::YZX => Self::new(Axis::Y, Parity::Even, Repeated::No, Frame::Static),
+            EulerRot::YZY => Self::new(Axis::Y, Parity::Even, Repeated::Yes, Frame::Static),
+            EulerRot::YXZ => Self::new(Axis::Y, Parity::Odd, Repeated::No, Frame::Static),
+            EulerRot::YXY => Self::new(Axis::Y, Parity::Odd, Repeated::Yes, Frame::Static),
+            EulerRot::ZXY => Self::new(Axis::Z, Parity::Even, Repeated::No, Frame::Static),
+            EulerRot::ZXZ => Self::new(Axis::Z, Parity::Even, Repeated::Yes, Frame::Static),
+            EulerRot::ZYX => Self::new(Axis::Z, Parity::Odd, Repeated::No, Frame::Static),
+            EulerRot::ZYZ => Self::new(Axis::Z, Parity::Odd, Repeated::Yes, Frame::Static),
+            EulerRot::ZYXEx => Self::new(Axis::X, Parity::Even, Repeated::No, Frame::Relative),
+            EulerRot::XYXEx => Self::new(Axis::X, Parity::Even, Repeated::Yes, Frame::Relative),
+            EulerRot::YZXEx => Self::new(Axis::X, Parity::Odd, Repeated::No, Frame::Relative),
+            EulerRot::XZXEx => Self::new(Axis::X, Parity::Odd, Repeated::Yes, Frame::Relative),
+            EulerRot::XZYEx => Self::new(Axis::Y, Parity::Even, Repeated::No, Frame::Relative),
+            EulerRot::YZYEx => Self::new(Axis::Y, Parity::Even, Repeated::Yes, Frame::Relative),
+            EulerRot::ZXYEx => Self::new(Axis::Y, Parity::Odd, Repeated::No, Frame::Relative),
+            EulerRot::YXYEx => Self::new(Axis::Y, Parity::Odd, Repeated::Yes, Frame::Relative),
+            EulerRot::YXZEx => Self::new(Axis::Z, Parity::Even, Repeated::No, Frame::Relative),
+            EulerRot::ZXZEx => Self::new(Axis::Z, Parity::Even, Repeated::Yes, Frame::Relative),
+            EulerRot::XYZEx => Self::new(Axis::Z, Parity::Odd, Repeated::No, Frame::Relative),
+            EulerRot::ZYZEx => Self::new(Axis::Z, Parity::Odd, Repeated::Yes, Frame::Relative),
+        }
+    }
+
+    const fn next_axis(i: usize) -> usize {
+        (i + 1) % 3
+    }
+
+    const fn prev_axis(i: usize) -> usize {
+        if i > 0 {
+            i - 1
+        } else {
+            2
+        }
+    }
+
+    const fn angle_order(self) -> (usize, usize, usize) {
+        let i = self.initial_axis as usize;
+        let j = if self.parity_even {
+            Order::next_axis(i)
+        } else {
+            Order::prev_axis(i)
+        };
+        let k = if self.parity_even {
+            Order::prev_axis(i)
+        } else {
+            Order::next_axis(i)
+        };
+        (i, j, k)
+    }
+}
+
+macro_rules! impl_mat3_from_euler {
+    ($scalar:ident, $mat3:ident, $vec3:ident) => {
+        impl FromEuler for $mat3 {
+            type Scalar = $scalar;
+            fn from_euler_angles(
+                euler: EulerRot,
+                x: Self::Scalar,
+                y: Self::Scalar,
+                z: Self::Scalar,
+            ) -> Self {
+                use crate::$scalar::math;
+
+                let order = Order::from_euler(euler);
+                let (i, j, k) = order.angle_order();
+
+                let mut angles = if order.frame_static {
+                    $vec3::new(x, y, z)
+                } else {
+                    $vec3::new(z, y, x)
+                };
+
+                // rotation direction is reverse from original paper
+                if order.parity_even {
+                    angles = -angles;
                 }
-                .clamp(-1.0, 1.0)
-            }
 
-            fn convert_quat(self, q: $quat) -> ($t, $t, $t) {
-                use crate::$t::math;
-                use EulerRot::*;
+                let (si, ci) = math::sin_cos(angles.x);
+                let (sj, cj) = math::sin_cos(angles.y);
+                let (sh, ch) = math::sin_cos(angles.z);
 
-                let sine_theta = self.sine_theta(q);
-                let second = math::asin(sine_theta);
+                let cc = ci * ch;
+                let cs = ci * sh;
+                let sc = si * ch;
+                let ss = si * sh;
 
-                if math::abs(sine_theta) > 0.99999 {
-                    let scale = 2.0 * math::signum(sine_theta);
+                let mut m = [[0.0; 3]; 3];
 
-                    return match self {
-                        ZYX => (scale * math::atan2(-q.x, q.w), second, 0.0),
-                        ZXY => (scale * math::atan2(q.y, q.w), second, 0.0),
-                        YXZ => (scale * math::atan2(-q.z, q.w), second, 0.0),
-                        YZX => (scale * math::atan2(q.x, q.w), second, 0.0),
-                        XYZ => (scale * math::atan2(q.z, q.w), second, 0.0),
-                        XZY => (scale * math::atan2(-q.y, q.w), second, 0.0),
-                    };
+                if order.initial_repeated {
+                    m[i][i] = cj;
+                    m[i][j] = sj * si;
+                    m[i][k] = sj * ci;
+                    m[j][i] = sj * sh;
+                    m[j][j] = -cj * ss + cc;
+                    m[j][k] = -cj * cs - sc;
+                    m[k][i] = -sj * ch;
+                    m[k][j] = cj * sc + cs;
+                    m[k][k] = cj * cc - ss;
+                } else {
+                    m[i][i] = cj * ch;
+                    m[i][j] = sj * sc - cs;
+                    m[i][k] = sj * cc + ss;
+                    m[j][i] = cj * sh;
+                    m[j][j] = sj * ss + cc;
+                    m[j][k] = sj * cs - sc;
+                    m[k][i] = -sj;
+                    m[k][j] = cj * si;
+                    m[k][k] = cj * ci;
                 }
 
-                let first = match self {
-                    ZYX => math::atan2(
-                        2.0 * (q.x * q.y + q.w * q.z),
-                        q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
-                    ),
-                    ZXY => math::atan2(
-                        -2.0 * (q.x * q.y - q.w * q.z),
-                        q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
-                    ),
-                    YXZ => math::atan2(
-                        2.0 * (q.x * q.z + q.w * q.y),
-                        q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
-                    ),
-                    YZX => math::atan2(
-                        -2.0 * (q.x * q.z - q.w * q.y),
-                        q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
-                    ),
-                    XYZ => math::atan2(
-                        -2.0 * (q.y * q.z - q.w * q.x),
-                        q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
-                    ),
-                    XZY => math::atan2(
-                        2.0 * (q.y * q.z + q.w * q.x),
-                        q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
-                    ),
-                };
+                $mat3::from_cols_array_2d(&m)
+            }
+        }
+    };
+}
+
+macro_rules! impl_mat4_from_euler {
+    ($scalar:ident, $mat4:ident, $mat3:ident) => {
+        impl FromEuler for $mat4 {
+            type Scalar = $scalar;
+            fn from_euler_angles(
+                euler: EulerRot,
+                x: Self::Scalar,
+                y: Self::Scalar,
+                z: Self::Scalar,
+            ) -> Self {
+                $mat4::from_mat3($mat3::from_euler_angles(euler, x, y, z))
+            }
+        }
+    };
+}
+
+macro_rules! impl_quat_from_euler {
+    ($scalar:ident, $quat:ident, $vec3:ident) => {
+        impl FromEuler for $quat {
+            type Scalar = $scalar;
+            fn from_euler_angles(
+                euler: EulerRot,
+                x: Self::Scalar,
+                y: Self::Scalar,
+                z: Self::Scalar,
+            ) -> Self {
+                use crate::$scalar::math;
 
-                let third = match self {
-                    ZYX => math::atan2(
-                        2.0 * (q.y * q.z + q.w * q.x),
-                        q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
-                    ),
-                    ZXY => math::atan2(
-                        -2.0 * (q.x * q.z - q.w * q.y),
-                        q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
-                    ),
-                    YXZ => math::atan2(
-                        2.0 * (q.x * q.y + q.w * q.z),
-                        q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
-                    ),
-                    YZX => math::atan2(
-                        -2.0 * (q.y * q.z - q.w * q.x),
-                        q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
-                    ),
-                    XYZ => math::atan2(
-                        -2.0 * (q.x * q.y - q.w * q.z),
-                        q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
-                    ),
-                    XZY => math::atan2(
-                        2.0 * (q.x * q.z + q.w * q.y),
-                        q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
-                    ),
+                let order = Order::from_euler(euler);
+                let (i, j, k) = order.angle_order();
+
+                let mut angles = if order.frame_static {
+                    $vec3::new(x, y, z)
+                } else {
+                    $vec3::new(z, y, x)
                 };
 
-                (first, second, third)
+                if order.parity_even {
+                    angles.y = -angles.y;
+                }
+
+                let ti = angles.x * 0.5;
+                let tj = angles.y * 0.5;
+                let th = angles.z * 0.5;
+                let (si, ci) = math::sin_cos(ti);
+                let (sj, cj) = math::sin_cos(tj);
+                let (sh, ch) = math::sin_cos(th);
+                let cc = ci * ch;
+                let cs = ci * sh;
+                let sc = si * ch;
+                let ss = si * sh;
+
+                let parity = if !order.parity_even { 1.0 } else { -1.0 };
+
+                let mut a = [0.0; 4];
+
+                if order.initial_repeated {
+                    a[i] = cj * (cs + sc);
+                    a[j] = sj * (cc + ss) * parity;
+                    a[k] = sj * (cs - sc);
+                    a[3] = cj * (cc - ss);
+                } else {
+                    a[i] = cj * sc - sj * cs;
+                    a[j] = (cj * ss + sj * cc) * parity;
+                    a[k] = cj * cs - sj * sc;
+                    a[3] = cj * cc + sj * ss;
+                }
+
+                $quat::from_array(a)
             }
         }
-        // End - impl EulerFromQuaternion
     };
 }
 
-macro_rules! impl_to_quat {
-    ($t:ty, $quat:ident) => {
-        impl EulerToQuaternion<$t> for EulerRot {
-            type Output = $quat;
-            #[inline(always)]
-            fn new_quat(self, u: $t, v: $t, w: $t) -> $quat {
-                use EulerRot::*;
-                #[inline(always)]
-                fn rot_x(a: $t) -> $quat {
-                    $quat::from_rotation_x(a)
-                }
-                #[inline(always)]
-                fn rot_y(a: $t) -> $quat {
-                    $quat::from_rotation_y(a)
+macro_rules! impl_mat3_to_euler {
+    ($scalar:ident, $mat3:ident, $vec3:ident) => {
+        impl ToEuler for $mat3 {
+            type Scalar = $scalar;
+            fn to_euler_angles(
+                self,
+                euler: EulerRot,
+            ) -> (Self::Scalar, Self::Scalar, Self::Scalar) {
+                use crate::$scalar::math;
+
+                let order = Order::from_euler(euler);
+                let (i, j, k) = order.angle_order();
+
+                let mut ea = $vec3::ZERO;
+                if order.initial_repeated {
+                    let sy = math::sqrt(
+                        self.col(i)[j] * self.col(i)[j] + self.col(i)[k] * self.col(i)[k],
+                    );
+                    if (sy > 16.0 * $scalar::EPSILON) {
+                        ea.x = math::atan2(self.col(i)[j], self.col(i)[k]);
+                        ea.y = math::atan2(sy, self.col(i)[i]);
+                        ea.z = math::atan2(self.col(j)[i], -self.col(k)[i]);
+                    } else {
+                        ea.x = math::atan2(-self.col(j)[k], self.col(j)[j]);
+                        ea.y = math::atan2(sy, self.col(i)[i]);
+                    }
+                } else {
+                    let cy = math::sqrt(
+                        self.col(i)[i] * self.col(i)[i] + self.col(j)[i] * self.col(j)[i],
+                    );
+                    if (cy > 16.0 * $scalar::EPSILON) {
+                        ea.x = math::atan2(self.col(k)[j], self.col(k)[k]);
+                        ea.y = math::atan2(-self.col(k)[i], cy);
+                        ea.z = math::atan2(self.col(j)[i], self.col(i)[i]);
+                    } else {
+                        ea.x = math::atan2(-self.col(j)[k], self.col(j)[j]);
+                        ea.y = math::atan2(-self.col(k)[i], cy);
+                    }
                 }
-                #[inline(always)]
-                fn rot_z(a: $t) -> $quat {
-                    $quat::from_rotation_z(a)
+
+                // reverse rotation angle of original code
+                if order.parity_even {
+                    ea = -ea;
                 }
-                match self {
-                    ZYX => rot_z(u) * rot_y(v) * rot_x(w),
-                    ZXY => rot_z(u) * rot_x(v) * rot_y(w),
-                    YXZ => rot_y(u) * rot_x(v) * rot_z(w),
-                    YZX => rot_y(u) * rot_z(v) * rot_x(w),
-                    XYZ => rot_x(u) * rot_y(v) * rot_z(w),
-                    XZY => rot_x(u) * rot_z(v) * rot_y(w),
+
+                if !order.frame_static {
+                    ea = ea.zyx();
                 }
-                .normalize()
+
+                (ea.x, ea.y, ea.z)
             }
         }
-        // End - impl EulerToQuaternion
     };
 }
 
-impl_from_quat!(f32, Quat);
-impl_from_quat!(f64, DQuat);
-impl_to_quat!(f32, Quat);
-impl_to_quat!(f64, DQuat);
+macro_rules! impl_mat4_to_euler {
+    ($scalar:ident, $mat4:ident, $mat3:ident) => {
+        impl ToEuler for $mat4 {
+            type Scalar = $scalar;
+            fn to_euler_angles(
+                self,
+                order: EulerRot,
+            ) -> (Self::Scalar, Self::Scalar, Self::Scalar) {
+                $mat3::from_mat4(self).to_euler_angles(order)
+            }
+        }
+    };
+}
+
+macro_rules! impl_quat_to_euler {
+    ($scalar:ident, $quat:ident, $mat3:ident) => {
+        impl ToEuler for $quat {
+            type Scalar = $scalar;
+            fn to_euler_angles(
+                self,
+                order: EulerRot,
+            ) -> (Self::Scalar, Self::Scalar, Self::Scalar) {
+                $mat3::from_quat(self).to_euler_angles(order)
+            }
+        }
+    };
+}
+
+impl_mat3_to_euler!(f32, Mat3, Vec3);
+impl_mat3_from_euler!(f32, Mat3, Vec3);
+impl_mat3_to_euler!(f32, Mat3A, Vec3A);
+impl_mat3_from_euler!(f32, Mat3A, Vec3A);
+impl_mat4_from_euler!(f32, Mat4, Mat3);
+impl_mat4_to_euler!(f32, Mat4, Mat3);
+impl_quat_to_euler!(f32, Quat, Mat3);
+impl_quat_from_euler!(f32, Quat, Vec3);
+
+impl_mat3_to_euler!(f64, DMat3, DVec3);
+impl_mat3_from_euler!(f64, DMat3, DVec3);
+impl_mat4_to_euler!(f64, DMat4, DMat3);
+impl_mat4_from_euler!(f64, DMat4, DMat3);
+impl_quat_to_euler!(f64, DQuat, DMat3);
+impl_quat_from_euler!(f64, DQuat, DVec3);
diff --git a/src/f32/coresimd/mat3a.rs b/src/f32/coresimd/mat3a.rs
index b5fb94f..841b35e 100644
--- a/src/f32/coresimd/mat3a.rs
+++ b/src/f32/coresimd/mat3a.rs
@@ -1,6 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
-use crate::{f32::math, swizzles::*, DMat3, EulerRot, Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec3A};
+use crate::{
+    euler::{FromEuler, ToEuler},
+    f32::math,
+    swizzles::*,
+    DMat3, EulerRot, Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec3A,
+};
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
 use core::iter::{Product, Sum};
@@ -220,8 +225,26 @@ impl Mat3A {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        let quat = Quat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the input matrix contains scales, shears, or other non-rotation transformations then
+    /// the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f32, f32, f32) {
+        glam_assert!(
+            self.x_axis.is_normalized()
+                && self.y_axis.is_normalized()
+                && self.z_axis.is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates a 3D rotation matrix from `angle` (in radians) around the x axis.
diff --git a/src/f32/coresimd/mat4.rs b/src/f32/coresimd/mat4.rs
index 7745dcb..2f106d7 100644
--- a/src/f32/coresimd/mat4.rs
+++ b/src/f32/coresimd/mat4.rs
@@ -1,7 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
 use crate::{
-    coresimd::*, f32::math, swizzles::*, DMat4, EulerRot, Mat3, Mat3A, Quat, Vec3, Vec3A, Vec4,
+    coresimd::*,
+    euler::{FromEuler, ToEuler},
+    f32::math,
+    swizzles::*,
+    DMat4, EulerRot, Mat3, Mat3A, Quat, Vec3, Vec3A, Vec4,
 };
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
@@ -382,8 +386,27 @@ impl Mat4 {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        let quat = Quat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f32, f32, f32) {
+        glam_assert!(
+            self.x_axis.xyz().is_normalized()
+                && self.y_axis.xyz().is_normalized()
+                && self.z_axis.xyz().is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates an affine transformation matrix containing a 3D rotation around the x axis of
diff --git a/src/f32/coresimd/quat.rs b/src/f32/coresimd/quat.rs
index 7814579..9ad130b 100644
--- a/src/f32/coresimd/quat.rs
+++ b/src/f32/coresimd/quat.rs
@@ -2,7 +2,7 @@
 
 use crate::{
     coresimd::*,
-    euler::{EulerFromQuaternion, EulerRot, EulerToQuaternion},
+    euler::{EulerRot, FromEuler, ToEuler},
     f32::math,
     DQuat, Mat3, Mat3A, Mat4, Vec2, Vec3, Vec3A, Vec4,
 };
@@ -175,14 +175,22 @@ impl Quat {
     #[inline]
     #[must_use]
     pub fn from_euler(euler: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        euler.new_quat(a, b, c)
+        Self::from_euler_angles(euler, a, b, c)
     }
 
     /// From the columns of a 3x3 rotation matrix.
+    ///
+    /// Note if the input axes contain scales, shears, or other non-rotation transformations then
+    /// the output of this function is ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any axis is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub(crate) fn from_rotation_axes(x_axis: Vec3, y_axis: Vec3, z_axis: Vec3) -> Self {
-        // Based on https://github.com/microsoft/DirectXMath `XM$quaternionRotationMatrix`
+        glam_assert!(x_axis.is_normalized() && y_axis.is_normalized() && z_axis.is_normalized());
+        // Based on https://github.com/microsoft/DirectXMath `XMQuaternionRotationMatrix`
         let (m00, m01, m02) = x_axis.into();
         let (m10, m11, m12) = y_axis.into();
         let (m20, m21, m22) = z_axis.into();
@@ -240,6 +248,13 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3(mat: &Mat3) -> Self {
@@ -247,13 +262,28 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 SIMD aligned rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3a(mat: &Mat3A) -> Self {
         Self::from_rotation_axes(mat.x_axis.into(), mat.y_axis.into(), mat.z_axis.into())
     }
 
-    /// Creates a quaternion from a 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    /// Creates a quaternion from the upper 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat4(mat: &Mat4) -> Self {
@@ -386,8 +416,8 @@ impl Quat {
     /// Returns the rotation angles for the given euler rotation sequence.
     #[inline]
     #[must_use]
-    pub fn to_euler(self, euler: EulerRot) -> (f32, f32, f32) {
-        euler.convert_quat(self)
+    pub fn to_euler(self, order: EulerRot) -> (f32, f32, f32) {
+        self.to_euler_angles(order)
     }
 
     /// `[x, y, z, w]`
@@ -714,6 +744,14 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix inside a 3D affine transform.
+    ///
+    /// Note if the input affine matrix contain scales, shears, or other non-rotation
+    /// transformations then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input affine matrix column is not normalized when `glam_assert` is
+    /// enabled.
     #[inline]
     #[must_use]
     pub fn from_affine3(a: &crate::Affine3A) -> Self {
diff --git a/src/f32/mat3.rs b/src/f32/mat3.rs
index e82465e..3205d02 100644
--- a/src/f32/mat3.rs
+++ b/src/f32/mat3.rs
@@ -1,6 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
-use crate::{f32::math, swizzles::*, DMat3, EulerRot, Mat2, Mat3A, Mat4, Quat, Vec2, Vec3, Vec3A};
+use crate::{
+    euler::{FromEuler, ToEuler},
+    f32::math,
+    swizzles::*,
+    DMat3, EulerRot, Mat2, Mat3A, Mat4, Quat, Vec2, Vec3, Vec3A,
+};
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
 use core::iter::{Product, Sum};
@@ -221,8 +226,26 @@ impl Mat3 {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        let quat = Quat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the input matrix contains scales, shears, or other non-rotation transformations then
+    /// the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f32, f32, f32) {
+        glam_assert!(
+            self.x_axis.is_normalized()
+                && self.y_axis.is_normalized()
+                && self.z_axis.is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates a 3D rotation matrix from `angle` (in radians) around the x axis.
diff --git a/src/f32/math.rs b/src/f32/math.rs
index 3e25875..127c9af 100644
--- a/src/f32/math.rs
+++ b/src/f32/math.rs
@@ -44,11 +44,6 @@ mod libm_math {
         super::acos_approx_f32(f)
     }
 
-    #[inline(always)]
-    pub(crate) fn asin(f: f32) -> f32 {
-        libm::asinf(f)
-    }
-
     #[inline(always)]
     pub(crate) fn atan2(f: f32, other: f32) -> f32 {
         libm::atan2f(f, other)
@@ -157,11 +152,6 @@ mod std_math {
         super::acos_approx_f32(f)
     }
 
-    #[inline(always)]
-    pub(crate) fn asin(f: f32) -> f32 {
-        f32::asin(f)
-    }
-
     #[inline(always)]
     pub(crate) fn atan2(f: f32, other: f32) -> f32 {
         f32::atan2(f, other)
diff --git a/src/f32/neon/mat3a.rs b/src/f32/neon/mat3a.rs
index b87bada..9423ffb 100644
--- a/src/f32/neon/mat3a.rs
+++ b/src/f32/neon/mat3a.rs
@@ -1,6 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
-use crate::{f32::math, swizzles::*, DMat3, EulerRot, Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec3A};
+use crate::{
+    euler::{FromEuler, ToEuler},
+    f32::math,
+    swizzles::*,
+    DMat3, EulerRot, Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec3A,
+};
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
 use core::iter::{Product, Sum};
@@ -220,8 +225,26 @@ impl Mat3A {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        let quat = Quat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the input matrix contains scales, shears, or other non-rotation transformations then
+    /// the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f32, f32, f32) {
+        glam_assert!(
+            self.x_axis.is_normalized()
+                && self.y_axis.is_normalized()
+                && self.z_axis.is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates a 3D rotation matrix from `angle` (in radians) around the x axis.
diff --git a/src/f32/neon/mat4.rs b/src/f32/neon/mat4.rs
index b3e12c5..0c6dd74 100644
--- a/src/f32/neon/mat4.rs
+++ b/src/f32/neon/mat4.rs
@@ -1,7 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
 use crate::{
-    f32::math, neon::*, swizzles::*, DMat4, EulerRot, Mat3, Mat3A, Quat, Vec3, Vec3A, Vec4,
+    euler::{FromEuler, ToEuler},
+    f32::math,
+    neon::*,
+    swizzles::*,
+    DMat4, EulerRot, Mat3, Mat3A, Quat, Vec3, Vec3A, Vec4,
 };
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
@@ -382,8 +386,27 @@ impl Mat4 {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        let quat = Quat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f32, f32, f32) {
+        glam_assert!(
+            self.x_axis.xyz().is_normalized()
+                && self.y_axis.xyz().is_normalized()
+                && self.z_axis.xyz().is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates an affine transformation matrix containing a 3D rotation around the x axis of
diff --git a/src/f32/neon/quat.rs b/src/f32/neon/quat.rs
index 9ca8241..1f86fb5 100644
--- a/src/f32/neon/quat.rs
+++ b/src/f32/neon/quat.rs
@@ -1,7 +1,7 @@
 // Generated from quat.rs.tera template. Edit the template, not the generated file.
 
 use crate::{
-    euler::{EulerFromQuaternion, EulerRot, EulerToQuaternion},
+    euler::{EulerRot, FromEuler, ToEuler},
     f32::math,
     neon::*,
     DQuat, Mat3, Mat3A, Mat4, Vec2, Vec3, Vec3A, Vec4,
@@ -180,14 +180,22 @@ impl Quat {
     #[inline]
     #[must_use]
     pub fn from_euler(euler: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        euler.new_quat(a, b, c)
+        Self::from_euler_angles(euler, a, b, c)
     }
 
     /// From the columns of a 3x3 rotation matrix.
+    ///
+    /// Note if the input axes contain scales, shears, or other non-rotation transformations then
+    /// the output of this function is ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any axis is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub(crate) fn from_rotation_axes(x_axis: Vec3, y_axis: Vec3, z_axis: Vec3) -> Self {
-        // Based on https://github.com/microsoft/DirectXMath `XM$quaternionRotationMatrix`
+        glam_assert!(x_axis.is_normalized() && y_axis.is_normalized() && z_axis.is_normalized());
+        // Based on https://github.com/microsoft/DirectXMath `XMQuaternionRotationMatrix`
         let (m00, m01, m02) = x_axis.into();
         let (m10, m11, m12) = y_axis.into();
         let (m20, m21, m22) = z_axis.into();
@@ -245,6 +253,13 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3(mat: &Mat3) -> Self {
@@ -252,13 +267,28 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 SIMD aligned rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3a(mat: &Mat3A) -> Self {
         Self::from_rotation_axes(mat.x_axis.into(), mat.y_axis.into(), mat.z_axis.into())
     }
 
-    /// Creates a quaternion from a 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    /// Creates a quaternion from the upper 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat4(mat: &Mat4) -> Self {
@@ -391,8 +421,8 @@ impl Quat {
     /// Returns the rotation angles for the given euler rotation sequence.
     #[inline]
     #[must_use]
-    pub fn to_euler(self, euler: EulerRot) -> (f32, f32, f32) {
-        euler.convert_quat(self)
+    pub fn to_euler(self, order: EulerRot) -> (f32, f32, f32) {
+        self.to_euler_angles(order)
     }
 
     /// `[x, y, z, w]`
@@ -742,6 +772,14 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix inside a 3D affine transform.
+    ///
+    /// Note if the input affine matrix contain scales, shears, or other non-rotation
+    /// transformations then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input affine matrix column is not normalized when `glam_assert` is
+    /// enabled.
     #[inline]
     #[must_use]
     pub fn from_affine3(a: &crate::Affine3A) -> Self {
diff --git a/src/f32/scalar/mat3a.rs b/src/f32/scalar/mat3a.rs
index 2801251..0a1e255 100644
--- a/src/f32/scalar/mat3a.rs
+++ b/src/f32/scalar/mat3a.rs
@@ -1,6 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
-use crate::{f32::math, swizzles::*, DMat3, EulerRot, Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec3A};
+use crate::{
+    euler::{FromEuler, ToEuler},
+    f32::math,
+    swizzles::*,
+    DMat3, EulerRot, Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec3A,
+};
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
 use core::iter::{Product, Sum};
@@ -221,8 +226,26 @@ impl Mat3A {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        let quat = Quat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the input matrix contains scales, shears, or other non-rotation transformations then
+    /// the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f32, f32, f32) {
+        glam_assert!(
+            self.x_axis.is_normalized()
+                && self.y_axis.is_normalized()
+                && self.z_axis.is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates a 3D rotation matrix from `angle` (in radians) around the x axis.
diff --git a/src/f32/scalar/mat4.rs b/src/f32/scalar/mat4.rs
index b39bb3f..beec9b8 100644
--- a/src/f32/scalar/mat4.rs
+++ b/src/f32/scalar/mat4.rs
@@ -1,6 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
-use crate::{f32::math, swizzles::*, DMat4, EulerRot, Mat3, Mat3A, Quat, Vec3, Vec3A, Vec4};
+use crate::{
+    euler::{FromEuler, ToEuler},
+    f32::math,
+    swizzles::*,
+    DMat4, EulerRot, Mat3, Mat3A, Quat, Vec3, Vec3A, Vec4,
+};
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
 use core::iter::{Product, Sum};
@@ -393,8 +398,27 @@ impl Mat4 {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        let quat = Quat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f32, f32, f32) {
+        glam_assert!(
+            self.x_axis.xyz().is_normalized()
+                && self.y_axis.xyz().is_normalized()
+                && self.z_axis.xyz().is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates an affine transformation matrix containing a 3D rotation around the x axis of
diff --git a/src/f32/scalar/quat.rs b/src/f32/scalar/quat.rs
index a18bbc9..7487480 100644
--- a/src/f32/scalar/quat.rs
+++ b/src/f32/scalar/quat.rs
@@ -1,7 +1,7 @@
 // Generated from quat.rs.tera template. Edit the template, not the generated file.
 
 use crate::{
-    euler::{EulerFromQuaternion, EulerRot, EulerToQuaternion},
+    euler::{EulerRot, FromEuler, ToEuler},
     f32::math,
     DQuat, Mat3, Mat3A, Mat4, Vec2, Vec3, Vec3A, Vec4,
 };
@@ -183,14 +183,22 @@ impl Quat {
     #[inline]
     #[must_use]
     pub fn from_euler(euler: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        euler.new_quat(a, b, c)
+        Self::from_euler_angles(euler, a, b, c)
     }
 
     /// From the columns of a 3x3 rotation matrix.
+    ///
+    /// Note if the input axes contain scales, shears, or other non-rotation transformations then
+    /// the output of this function is ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any axis is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub(crate) fn from_rotation_axes(x_axis: Vec3, y_axis: Vec3, z_axis: Vec3) -> Self {
-        // Based on https://github.com/microsoft/DirectXMath `XM$quaternionRotationMatrix`
+        glam_assert!(x_axis.is_normalized() && y_axis.is_normalized() && z_axis.is_normalized());
+        // Based on https://github.com/microsoft/DirectXMath `XMQuaternionRotationMatrix`
         let (m00, m01, m02) = x_axis.into();
         let (m10, m11, m12) = y_axis.into();
         let (m20, m21, m22) = z_axis.into();
@@ -248,6 +256,13 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3(mat: &Mat3) -> Self {
@@ -255,13 +270,28 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 SIMD aligned rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3a(mat: &Mat3A) -> Self {
         Self::from_rotation_axes(mat.x_axis.into(), mat.y_axis.into(), mat.z_axis.into())
     }
 
-    /// Creates a quaternion from a 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    /// Creates a quaternion from the upper 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat4(mat: &Mat4) -> Self {
@@ -394,8 +424,8 @@ impl Quat {
     /// Returns the rotation angles for the given euler rotation sequence.
     #[inline]
     #[must_use]
-    pub fn to_euler(self, euler: EulerRot) -> (f32, f32, f32) {
-        euler.convert_quat(self)
+    pub fn to_euler(self, order: EulerRot) -> (f32, f32, f32) {
+        self.to_euler_angles(order)
     }
 
     /// `[x, y, z, w]`
@@ -698,6 +728,14 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix inside a 3D affine transform.
+    ///
+    /// Note if the input affine matrix contain scales, shears, or other non-rotation
+    /// transformations then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input affine matrix column is not normalized when `glam_assert` is
+    /// enabled.
     #[inline]
     #[must_use]
     pub fn from_affine3(a: &crate::Affine3A) -> Self {
diff --git a/src/f32/sse2/mat3a.rs b/src/f32/sse2/mat3a.rs
index 24b5ce8..8d235c5 100644
--- a/src/f32/sse2/mat3a.rs
+++ b/src/f32/sse2/mat3a.rs
@@ -1,6 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
-use crate::{f32::math, swizzles::*, DMat3, EulerRot, Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec3A};
+use crate::{
+    euler::{FromEuler, ToEuler},
+    f32::math,
+    swizzles::*,
+    DMat3, EulerRot, Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec3A,
+};
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
 use core::iter::{Product, Sum};
@@ -223,8 +228,26 @@ impl Mat3A {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        let quat = Quat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the input matrix contains scales, shears, or other non-rotation transformations then
+    /// the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f32, f32, f32) {
+        glam_assert!(
+            self.x_axis.is_normalized()
+                && self.y_axis.is_normalized()
+                && self.z_axis.is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates a 3D rotation matrix from `angle` (in radians) around the x axis.
diff --git a/src/f32/sse2/mat4.rs b/src/f32/sse2/mat4.rs
index c499b8e..81900f8 100644
--- a/src/f32/sse2/mat4.rs
+++ b/src/f32/sse2/mat4.rs
@@ -1,7 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
 use crate::{
-    f32::math, sse2::*, swizzles::*, DMat4, EulerRot, Mat3, Mat3A, Quat, Vec3, Vec3A, Vec4,
+    euler::{FromEuler, ToEuler},
+    f32::math,
+    sse2::*,
+    swizzles::*,
+    DMat4, EulerRot, Mat3, Mat3A, Quat, Vec3, Vec3A, Vec4,
 };
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
@@ -385,8 +389,27 @@ impl Mat4 {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        let quat = Quat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f32, f32, f32) {
+        glam_assert!(
+            self.x_axis.xyz().is_normalized()
+                && self.y_axis.xyz().is_normalized()
+                && self.z_axis.xyz().is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates an affine transformation matrix containing a 3D rotation around the x axis of
diff --git a/src/f32/sse2/quat.rs b/src/f32/sse2/quat.rs
index 6c8cdcb..5b369a4 100644
--- a/src/f32/sse2/quat.rs
+++ b/src/f32/sse2/quat.rs
@@ -1,7 +1,7 @@
 // Generated from quat.rs.tera template. Edit the template, not the generated file.
 
 use crate::{
-    euler::{EulerFromQuaternion, EulerRot, EulerToQuaternion},
+    euler::{EulerRot, FromEuler, ToEuler},
     f32::math,
     sse2::*,
     DQuat, Mat3, Mat3A, Mat4, Vec2, Vec3, Vec3A, Vec4,
@@ -183,14 +183,22 @@ impl Quat {
     #[inline]
     #[must_use]
     pub fn from_euler(euler: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        euler.new_quat(a, b, c)
+        Self::from_euler_angles(euler, a, b, c)
     }
 
     /// From the columns of a 3x3 rotation matrix.
+    ///
+    /// Note if the input axes contain scales, shears, or other non-rotation transformations then
+    /// the output of this function is ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any axis is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub(crate) fn from_rotation_axes(x_axis: Vec3, y_axis: Vec3, z_axis: Vec3) -> Self {
-        // Based on https://github.com/microsoft/DirectXMath `XM$quaternionRotationMatrix`
+        glam_assert!(x_axis.is_normalized() && y_axis.is_normalized() && z_axis.is_normalized());
+        // Based on https://github.com/microsoft/DirectXMath `XMQuaternionRotationMatrix`
         let (m00, m01, m02) = x_axis.into();
         let (m10, m11, m12) = y_axis.into();
         let (m20, m21, m22) = z_axis.into();
@@ -248,6 +256,13 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3(mat: &Mat3) -> Self {
@@ -255,13 +270,28 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 SIMD aligned rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3a(mat: &Mat3A) -> Self {
         Self::from_rotation_axes(mat.x_axis.into(), mat.y_axis.into(), mat.z_axis.into())
     }
 
-    /// Creates a quaternion from a 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    /// Creates a quaternion from the upper 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat4(mat: &Mat4) -> Self {
@@ -394,8 +424,8 @@ impl Quat {
     /// Returns the rotation angles for the given euler rotation sequence.
     #[inline]
     #[must_use]
-    pub fn to_euler(self, euler: EulerRot) -> (f32, f32, f32) {
-        euler.convert_quat(self)
+    pub fn to_euler(self, order: EulerRot) -> (f32, f32, f32) {
+        self.to_euler_angles(order)
     }
 
     /// `[x, y, z, w]`
@@ -737,6 +767,14 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix inside a 3D affine transform.
+    ///
+    /// Note if the input affine matrix contain scales, shears, or other non-rotation
+    /// transformations then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input affine matrix column is not normalized when `glam_assert` is
+    /// enabled.
     #[inline]
     #[must_use]
     pub fn from_affine3(a: &crate::Affine3A) -> Self {
diff --git a/src/f32/wasm32/mat3a.rs b/src/f32/wasm32/mat3a.rs
index 9cd6573..4e35b9b 100644
--- a/src/f32/wasm32/mat3a.rs
+++ b/src/f32/wasm32/mat3a.rs
@@ -1,6 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
-use crate::{f32::math, swizzles::*, DMat3, EulerRot, Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec3A};
+use crate::{
+    euler::{FromEuler, ToEuler},
+    f32::math,
+    swizzles::*,
+    DMat3, EulerRot, Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec3A,
+};
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
 use core::iter::{Product, Sum};
@@ -220,8 +225,26 @@ impl Mat3A {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        let quat = Quat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the input matrix contains scales, shears, or other non-rotation transformations then
+    /// the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f32, f32, f32) {
+        glam_assert!(
+            self.x_axis.is_normalized()
+                && self.y_axis.is_normalized()
+                && self.z_axis.is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates a 3D rotation matrix from `angle` (in radians) around the x axis.
diff --git a/src/f32/wasm32/mat4.rs b/src/f32/wasm32/mat4.rs
index 0fd7308..290dda3 100644
--- a/src/f32/wasm32/mat4.rs
+++ b/src/f32/wasm32/mat4.rs
@@ -1,7 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
 use crate::{
-    f32::math, swizzles::*, wasm32::*, DMat4, EulerRot, Mat3, Mat3A, Quat, Vec3, Vec3A, Vec4,
+    euler::{FromEuler, ToEuler},
+    f32::math,
+    swizzles::*,
+    wasm32::*,
+    DMat4, EulerRot, Mat3, Mat3A, Quat, Vec3, Vec3A, Vec4,
 };
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
@@ -382,8 +386,27 @@ impl Mat4 {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        let quat = Quat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f32, f32, f32) {
+        glam_assert!(
+            self.x_axis.xyz().is_normalized()
+                && self.y_axis.xyz().is_normalized()
+                && self.z_axis.xyz().is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates an affine transformation matrix containing a 3D rotation around the x axis of
diff --git a/src/f32/wasm32/quat.rs b/src/f32/wasm32/quat.rs
index 4bdf3eb..e05b2bb 100644
--- a/src/f32/wasm32/quat.rs
+++ b/src/f32/wasm32/quat.rs
@@ -1,7 +1,7 @@
 // Generated from quat.rs.tera template. Edit the template, not the generated file.
 
 use crate::{
-    euler::{EulerFromQuaternion, EulerRot, EulerToQuaternion},
+    euler::{EulerRot, FromEuler, ToEuler},
     f32::math,
     wasm32::*,
     DQuat, Mat3, Mat3A, Mat4, Vec2, Vec3, Vec3A, Vec4,
@@ -175,14 +175,22 @@ impl Quat {
     #[inline]
     #[must_use]
     pub fn from_euler(euler: EulerRot, a: f32, b: f32, c: f32) -> Self {
-        euler.new_quat(a, b, c)
+        Self::from_euler_angles(euler, a, b, c)
     }
 
     /// From the columns of a 3x3 rotation matrix.
+    ///
+    /// Note if the input axes contain scales, shears, or other non-rotation transformations then
+    /// the output of this function is ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any axis is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub(crate) fn from_rotation_axes(x_axis: Vec3, y_axis: Vec3, z_axis: Vec3) -> Self {
-        // Based on https://github.com/microsoft/DirectXMath `XM$quaternionRotationMatrix`
+        glam_assert!(x_axis.is_normalized() && y_axis.is_normalized() && z_axis.is_normalized());
+        // Based on https://github.com/microsoft/DirectXMath `XMQuaternionRotationMatrix`
         let (m00, m01, m02) = x_axis.into();
         let (m10, m11, m12) = y_axis.into();
         let (m20, m21, m22) = z_axis.into();
@@ -240,6 +248,13 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3(mat: &Mat3) -> Self {
@@ -247,13 +262,28 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 SIMD aligned rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3a(mat: &Mat3A) -> Self {
         Self::from_rotation_axes(mat.x_axis.into(), mat.y_axis.into(), mat.z_axis.into())
     }
 
-    /// Creates a quaternion from a 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    /// Creates a quaternion from the upper 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat4(mat: &Mat4) -> Self {
@@ -386,8 +416,8 @@ impl Quat {
     /// Returns the rotation angles for the given euler rotation sequence.
     #[inline]
     #[must_use]
-    pub fn to_euler(self, euler: EulerRot) -> (f32, f32, f32) {
-        euler.convert_quat(self)
+    pub fn to_euler(self, order: EulerRot) -> (f32, f32, f32) {
+        self.to_euler_angles(order)
     }
 
     /// `[x, y, z, w]`
@@ -728,6 +758,14 @@ impl Quat {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix inside a 3D affine transform.
+    ///
+    /// Note if the input affine matrix contain scales, shears, or other non-rotation
+    /// transformations then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input affine matrix column is not normalized when `glam_assert` is
+    /// enabled.
     #[inline]
     #[must_use]
     pub fn from_affine3(a: &crate::Affine3A) -> Self {
diff --git a/src/f64/dmat3.rs b/src/f64/dmat3.rs
index 25162f9..687d176 100644
--- a/src/f64/dmat3.rs
+++ b/src/f64/dmat3.rs
@@ -1,6 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
-use crate::{f64::math, swizzles::*, DMat2, DMat4, DQuat, DVec2, DVec3, EulerRot, Mat3};
+use crate::{
+    euler::{FromEuler, ToEuler},
+    f64::math,
+    swizzles::*,
+    DMat2, DMat4, DQuat, DVec2, DVec3, EulerRot, Mat3,
+};
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
 use core::iter::{Product, Sum};
@@ -221,8 +226,26 @@ impl DMat3 {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f64, b: f64, c: f64) -> Self {
-        let quat = DQuat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the input matrix contains scales, shears, or other non-rotation transformations then
+    /// the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f64, f64, f64) {
+        glam_assert!(
+            self.x_axis.is_normalized()
+                && self.y_axis.is_normalized()
+                && self.z_axis.is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates a 3D rotation matrix from `angle` (in radians) around the x axis.
diff --git a/src/f64/dmat4.rs b/src/f64/dmat4.rs
index d1d10b2..82c0071 100644
--- a/src/f64/dmat4.rs
+++ b/src/f64/dmat4.rs
@@ -1,6 +1,11 @@
 // Generated from mat.rs.tera template. Edit the template, not the generated file.
 
-use crate::{f64::math, swizzles::*, DMat3, DQuat, DVec3, DVec4, EulerRot, Mat4};
+use crate::{
+    euler::{FromEuler, ToEuler},
+    f64::math,
+    swizzles::*,
+    DMat3, DQuat, DVec3, DVec4, EulerRot, Mat4,
+};
 #[cfg(not(target_arch = "spirv"))]
 use core::fmt;
 use core::iter::{Product, Sum};
@@ -375,8 +380,27 @@ impl DMat4 {
     #[inline]
     #[must_use]
     pub fn from_euler(order: EulerRot, a: f64, b: f64, c: f64) -> Self {
-        let quat = DQuat::from_euler(order, a, b, c);
-        Self::from_quat(quat)
+        Self::from_euler_angles(order, a, b, c)
+    }
+
+    /// Extract Euler angles with the given Euler rotation order.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting Euler angles will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
+    #[inline]
+    #[must_use]
+    pub fn to_euler(&self, order: EulerRot) -> (f64, f64, f64) {
+        glam_assert!(
+            self.x_axis.xyz().is_normalized()
+                && self.y_axis.xyz().is_normalized()
+                && self.z_axis.xyz().is_normalized()
+        );
+        self.to_euler_angles(order)
     }
 
     /// Creates an affine transformation matrix containing a 3D rotation around the x axis of
diff --git a/src/f64/dquat.rs b/src/f64/dquat.rs
index b4433be..55bd338 100644
--- a/src/f64/dquat.rs
+++ b/src/f64/dquat.rs
@@ -1,7 +1,7 @@
 // Generated from quat.rs.tera template. Edit the template, not the generated file.
 
 use crate::{
-    euler::{EulerFromQuaternion, EulerRot, EulerToQuaternion},
+    euler::{EulerRot, FromEuler, ToEuler},
     f64::math,
     DMat3, DMat4, DVec2, DVec3, DVec4, Quat,
 };
@@ -179,14 +179,22 @@ impl DQuat {
     #[inline]
     #[must_use]
     pub fn from_euler(euler: EulerRot, a: f64, b: f64, c: f64) -> Self {
-        euler.new_quat(a, b, c)
+        Self::from_euler_angles(euler, a, b, c)
     }
 
     /// From the columns of a 3x3 rotation matrix.
+    ///
+    /// Note if the input axes contain scales, shears, or other non-rotation transformations then
+    /// the output of this function is ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any axis is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub(crate) fn from_rotation_axes(x_axis: DVec3, y_axis: DVec3, z_axis: DVec3) -> Self {
-        // Based on https://github.com/microsoft/DirectXMath `XM$quaternionRotationMatrix`
+        glam_assert!(x_axis.is_normalized() && y_axis.is_normalized() && z_axis.is_normalized());
+        // Based on https://github.com/microsoft/DirectXMath `XMQuaternionRotationMatrix`
         let (m00, m01, m02) = x_axis.into();
         let (m10, m11, m12) = y_axis.into();
         let (m20, m21, m22) = z_axis.into();
@@ -244,13 +252,28 @@ impl DQuat {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix.
+    ///
+    /// Note if the input matrix contain scales, shears, or other non-rotation transformations then
+    /// the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input matrix column is not normalized when `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat3(mat: &DMat3) -> Self {
         Self::from_rotation_axes(mat.x_axis, mat.y_axis, mat.z_axis)
     }
 
-    /// Creates a quaternion from a 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    /// Creates a quaternion from the upper 3x3 rotation matrix inside a homogeneous 4x4 matrix.
+    ///
+    /// Note if the upper 3x3 matrix contain scales, shears, or other non-rotation transformations
+    /// then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any column of the upper 3x3 rotation matrix is not normalized when
+    /// `glam_assert` is enabled.
     #[inline]
     #[must_use]
     pub fn from_mat4(mat: &DMat4) -> Self {
@@ -383,8 +406,8 @@ impl DQuat {
     /// Returns the rotation angles for the given euler rotation sequence.
     #[inline]
     #[must_use]
-    pub fn to_euler(self, euler: EulerRot) -> (f64, f64, f64) {
-        euler.convert_quat(self)
+    pub fn to_euler(self, order: EulerRot) -> (f64, f64, f64) {
+        self.to_euler_angles(order)
     }
 
     /// `[x, y, z, w]`
@@ -687,6 +710,14 @@ impl DQuat {
     }
 
     /// Creates a quaternion from a 3x3 rotation matrix inside a 3D affine transform.
+    ///
+    /// Note if the input affine matrix contain scales, shears, or other non-rotation
+    /// transformations then the resulting quaternion will be ill-defined.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if any input affine matrix column is not normalized when `glam_assert` is
+    /// enabled.
     #[inline]
     #[must_use]
     pub fn from_affine3(a: &crate::DAffine3) -> Self {
diff --git a/src/f64/math.rs b/src/f64/math.rs
index 45ace5c..2885342 100644
--- a/src/f64/math.rs
+++ b/src/f64/math.rs
@@ -10,11 +10,6 @@ mod libm_math {
         libm::acos(f.clamp(-1.0, 1.0))
     }
 
-    #[inline(always)]
-    pub(crate) fn asin(f: f64) -> f64 {
-        libm::asin(f)
-    }
-
     #[inline(always)]
     pub(crate) fn atan2(f: f64, other: f64) -> f64 {
         libm::atan2(f, other)
@@ -122,11 +117,6 @@ mod std_math {
         f64::acos(f64::clamp(f, -1.0, 1.0))
     }
 
-    #[inline(always)]
-    pub(crate) fn asin(f: f64) -> f64 {
-        f64::asin(f)
-    }
-
     #[inline(always)]
     pub(crate) fn atan2(f: f64, other: f64) -> f64 {
         f64::atan2(f, other)
diff --git a/src/features/impl_rand.rs b/src/features/impl_rand.rs
index 3c6c19a..387c365 100644
--- a/src/features/impl_rand.rs
+++ b/src/features/impl_rand.rs
@@ -84,10 +84,12 @@ macro_rules! impl_float_types {
         impl Distribution<$quat> for Standard {
             #[inline]
             fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $quat {
-                let yaw = -PI + rng.gen::<$t>() * 2.0 * PI;
-                let pitch = -PI + rng.gen::<$t>() * 2.0 * PI;
-                let roll = -PI + rng.gen::<$t>() * 2.0 * PI;
-                $quat::from_euler(crate::EulerRot::YXZ, yaw, pitch, roll)
+                let z = rng.gen_range::<$t, _>(-1.0..=1.0);
+                let (y, x) = math::sin_cos(rng.gen_range::<$t, _>(0.0..TAU));
+                let r = math::sqrt(1.0 - z * z);
+                let axis = $vec3::new(r * x, r * y, z);
+                let angle = rng.gen_range::<$t, _>(0.0..TAU);
+                $quat::from_axis_angle(axis, angle)
             }
         }
 
@@ -139,8 +141,9 @@ macro_rules! impl_float_types {
 }
 
 mod f32 {
+    use crate::f32::math;
     use crate::{Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec3A, Vec4};
-    use core::f32::consts::PI;
+    use core::f32::consts::TAU;
     use rand::{
         distributions::{Distribution, Standard},
         Rng,
@@ -168,8 +171,9 @@ mod f32 {
 }
 
 mod f64 {
+    use crate::f64::math;
     use crate::{DMat2, DMat3, DMat4, DQuat, DVec2, DVec3, DVec4};
-    use core::f64::consts::PI;
+    use core::f64::consts::TAU;
     use rand::{
         distributions::{Distribution, Standard},
         Rng,
diff --git a/src/features/impl_serde.rs b/src/features/impl_serde.rs
index ab0db6c..497a909 100644
--- a/src/features/impl_serde.rs
+++ b/src/features/impl_serde.rs
@@ -1117,6 +1117,60 @@ mod euler {
                 EulerRot::XZY => {
                     serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 5u32, "XZY")
                 }
+                EulerRot::ZYZ => {
+                    serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 6u32, "ZYZ")
+                }
+                EulerRot::ZXZ => {
+                    serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 7u32, "ZXZ")
+                }
+                EulerRot::YXY => {
+                    serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 8u32, "YXY")
+                }
+                EulerRot::YZY => {
+                    serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 9u32, "YZY")
+                }
+                EulerRot::XYX => {
+                    serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 10u32, "XYX")
+                }
+                EulerRot::XZX => {
+                    serde::Serializer::serialize_unit_variant(serializer, "EulerRot", 11u32, "XZX")
+                }
+                EulerRot::ZYXEx => serde::Serializer::serialize_unit_variant(
+                    serializer, "EulerRot", 12u32, "ZYXEx",
+                ),
+                EulerRot::ZXYEx => serde::Serializer::serialize_unit_variant(
+                    serializer, "EulerRot", 13u32, "ZXYEx",
+                ),
+                EulerRot::YXZEx => serde::Serializer::serialize_unit_variant(
+                    serializer, "EulerRot", 14u32, "YXZEx",
+                ),
+                EulerRot::YZXEx => serde::Serializer::serialize_unit_variant(
+                    serializer, "EulerRot", 15u32, "YZXEx",
+                ),
+                EulerRot::XYZEx => serde::Serializer::serialize_unit_variant(
+                    serializer, "EulerRot", 16u32, "XYZEx",
+                ),
+                EulerRot::XZYEx => serde::Serializer::serialize_unit_variant(
+                    serializer, "EulerRot", 17u32, "XZYEx",
+                ),
+                EulerRot::ZYZEx => serde::Serializer::serialize_unit_variant(
+                    serializer, "EulerRot", 18u32, "ZYZEx",
+                ),
+                EulerRot::ZXZEx => serde::Serializer::serialize_unit_variant(
+                    serializer, "EulerRot", 19u32, "ZXZEx",
+                ),
+                EulerRot::YXYEx => serde::Serializer::serialize_unit_variant(
+                    serializer, "EulerRot", 20u32, "YXYEx",
+                ),
+                EulerRot::YZYEx => serde::Serializer::serialize_unit_variant(
+                    serializer, "EulerRot", 21u32, "YZYEx",
+                ),
+                EulerRot::XYXEx => serde::Serializer::serialize_unit_variant(
+                    serializer, "EulerRot", 22u32, "XYXEx",
+                ),
+                EulerRot::XZXEx => serde::Serializer::serialize_unit_variant(
+                    serializer, "EulerRot", 23u32, "XZXEx",
+                ),
             }
         }
     }
@@ -1134,6 +1188,24 @@ mod euler {
                 YZX,
                 XYZ,
                 XZY,
+                ZYZ,
+                ZXZ,
+                YXY,
+                YZY,
+                XYX,
+                XZX,
+                ZYXEx,
+                ZXYEx,
+                YXZEx,
+                YZXEx,
+                XYZEx,
+                XZYEx,
+                ZYZEx,
+                ZXZEx,
+                YXYEx,
+                YZYEx,
+                XYXEx,
+                XZXEx,
             }
             struct FieldVisitor;
 
@@ -1153,9 +1225,28 @@ mod euler {
                         3u64 => Ok(Field::YZX),
                         4u64 => Ok(Field::XYZ),
                         5u64 => Ok(Field::XZY),
+                        6u64 => Ok(Field::ZYZ),
+                        7u64 => Ok(Field::ZXZ),
+                        8u64 => Ok(Field::YXY),
+                        9u64 => Ok(Field::YZY),
+                        10u64 => Ok(Field::XYX),
+                        11u64 => Ok(Field::XZX),
+
+                        12u64 => Ok(Field::ZYXEx),
+                        13u64 => Ok(Field::ZXYEx),
+                        14u64 => Ok(Field::YXZEx),
+                        15u64 => Ok(Field::YZXEx),
+                        16u64 => Ok(Field::XYZEx),
+                        17u64 => Ok(Field::XZYEx),
+                        18u64 => Ok(Field::ZYZEx),
+                        19u64 => Ok(Field::ZXZEx),
+                        20u64 => Ok(Field::YXYEx),
+                        21u64 => Ok(Field::YZYEx),
+                        22u64 => Ok(Field::XYXEx),
+                        23u64 => Ok(Field::XZXEx),
                         _ => Err(serde::de::Error::invalid_value(
                             serde::de::Unexpected::Unsigned(value),
-                            &"variant index 0 <= i < 6",
+                            &"variant index 0 <= i < 24",
                         )),
                     }
                 }
@@ -1170,6 +1261,24 @@ mod euler {
                         "YZX" => Ok(Field::YZX),
                         "XYZ" => Ok(Field::XYZ),
                         "XZY" => Ok(Field::XZY),
+                        "ZYZ" => Ok(Field::ZYZ),
+                        "ZXZ" => Ok(Field::ZXZ),
+                        "YXY" => Ok(Field::YXY),
+                        "YZY" => Ok(Field::YZY),
+                        "XYX" => Ok(Field::XYX),
+                        "XZX" => Ok(Field::XZX),
+                        "ZYXEx" => Ok(Field::ZYXEx),
+                        "ZXYEx" => Ok(Field::ZXYEx),
+                        "YXZEx" => Ok(Field::YXZEx),
+                        "YZXEx" => Ok(Field::YZXEx),
+                        "XYZEx" => Ok(Field::XYZEx),
+                        "XZYEx" => Ok(Field::XZYEx),
+                        "ZYZEx" => Ok(Field::ZYZEx),
+                        "ZXZEx" => Ok(Field::ZXZEx),
+                        "YXYEx" => Ok(Field::YXYEx),
+                        "YZYEx" => Ok(Field::YZYEx),
+                        "XYXEx" => Ok(Field::XYXEx),
+                        "XZXEx" => Ok(Field::XZXEx),
                         _ => Err(serde::de::Error::unknown_variant(value, VARIANTS)),
                     }
                 }
@@ -1184,6 +1293,24 @@ mod euler {
                         b"YZX" => Ok(Field::YZX),
                         b"XYZ" => Ok(Field::XYZ),
                         b"XZY" => Ok(Field::XZY),
+                        b"ZYZ" => Ok(Field::ZYZ),
+                        b"ZXZ" => Ok(Field::ZXZ),
+                        b"YXY" => Ok(Field::YXY),
+                        b"YZY" => Ok(Field::YZY),
+                        b"XYX" => Ok(Field::XYX),
+                        b"XZX" => Ok(Field::XZX),
+                        b"ZYXEx" => Ok(Field::ZYXEx),
+                        b"ZXYEx" => Ok(Field::ZXYEx),
+                        b"YXZEx" => Ok(Field::YXZEx),
+                        b"YZXEx" => Ok(Field::YZXEx),
+                        b"XYZEx" => Ok(Field::XYZEx),
+                        b"XZYEx" => Ok(Field::XZYEx),
+                        b"ZYZEx" => Ok(Field::ZYZEx),
+                        b"ZXZEx" => Ok(Field::ZXZEx),
+                        b"YXYEx" => Ok(Field::YXYEx),
+                        b"YZYEx" => Ok(Field::YZYEx),
+                        b"XYXEx" => Ok(Field::XYXEx),
+                        b"XZXEx" => Ok(Field::XZXEx),
                         _ => {
                             #[cfg(feature = "std")]
                             let value = &String::from_utf8_lossy(value);
@@ -1245,10 +1372,87 @@ mod euler {
                             serde::de::VariantAccess::unit_variant(variant)?;
                             Ok(EulerRot::XZY)
                         }
+
+                        (Field::ZYZ, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::ZYZ)
+                        }
+                        (Field::ZXZ, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::ZXZ)
+                        }
+                        (Field::YXY, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::YXY)
+                        }
+                        (Field::YZY, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::YZY)
+                        }
+                        (Field::XYX, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::XYX)
+                        }
+                        (Field::XZX, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::XZX)
+                        }
+                        (Field::ZYXEx, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::ZYXEx)
+                        }
+                        (Field::ZXYEx, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::ZXYEx)
+                        }
+                        (Field::YXZEx, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::YXZEx)
+                        }
+                        (Field::YZXEx, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::YZXEx)
+                        }
+                        (Field::XYZEx, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::XYZEx)
+                        }
+                        (Field::XZYEx, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::XZYEx)
+                        }
+                        (Field::ZYZEx, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::ZYZEx)
+                        }
+                        (Field::ZXZEx, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::ZXZEx)
+                        }
+                        (Field::YXYEx, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::YXYEx)
+                        }
+                        (Field::YZYEx, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::YZYEx)
+                        }
+                        (Field::XYXEx, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::XYXEx)
+                        }
+                        (Field::XZXEx, variant) => {
+                            serde::de::VariantAccess::unit_variant(variant)?;
+                            Ok(EulerRot::XZXEx)
+                        }
                     }
                 }
             }
-            const VARIANTS: &[&str] = &["ZYX", "ZXY", "YXZ", "YZX", "XYZ", "XZY"];
+            const VARIANTS: &[&str] = &[
+                "ZYX", "ZXY", "YXZ", "YZX", "XYZ", "XZY", "ZYZ", "ZXZ", "YXY", "YZY", "XYX", "XZX",
+                "ZYXEx", "ZXYEx", "YXZEx", "YZXEx", "XYZEx", "XZYEx", "ZYZEx", "ZXZEx", "YXYEx",
+                "YZYEx", "XYXEx", "XZXEx",
+            ];
             serde::Deserializer::deserialize_enum(
                 deserializer,
                 "EulerRot",
@@ -1263,10 +1467,37 @@ mod euler {
 
     #[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);
+        const PAIRS: [(EulerRot, &str); 24] = [
+            (EulerRot::ZYX, "\"ZYX\""),
+            (EulerRot::ZXY, "\"ZXY\""),
+            (EulerRot::YXZ, "\"YXZ\""),
+            (EulerRot::YZX, "\"YZX\""),
+            (EulerRot::XYZ, "\"XYZ\""),
+            (EulerRot::XZY, "\"XZY\""),
+            (EulerRot::ZYZ, "\"ZYZ\""),
+            (EulerRot::ZXZ, "\"ZXZ\""),
+            (EulerRot::YXY, "\"YXY\""),
+            (EulerRot::YZY, "\"YZY\""),
+            (EulerRot::XYX, "\"XYX\""),
+            (EulerRot::XZX, "\"XZX\""),
+            (EulerRot::ZYXEx, "\"ZYXEx\""),
+            (EulerRot::ZXYEx, "\"ZXYEx\""),
+            (EulerRot::YXZEx, "\"YXZEx\""),
+            (EulerRot::YZXEx, "\"YZXEx\""),
+            (EulerRot::XYZEx, "\"XYZEx\""),
+            (EulerRot::XZYEx, "\"XZYEx\""),
+            (EulerRot::ZYZEx, "\"ZYZEx\""),
+            (EulerRot::ZXZEx, "\"ZXZEx\""),
+            (EulerRot::YXYEx, "\"YXYEx\""),
+            (EulerRot::YZYEx, "\"YZYEx\""),
+            (EulerRot::XYXEx, "\"XYXEx\""),
+            (EulerRot::XZXEx, "\"XZXEx\""),
+        ];
+        for (enum_value, enum_string) in PAIRS {
+            let serialized = serde_json::to_string(&enum_value).unwrap();
+            assert_eq!(enum_string, serialized);
+            let deserialized = serde_json::from_str(&serialized).unwrap();
+            assert_eq!(enum_value, deserialized);
+        }
     }
 }


Test patch:
diff --git a/tests/euler.rs b/tests/euler.rs
index b66880e..4cf6d35 100644
--- a/tests/euler.rs
+++ b/tests/euler.rs
@@ -1,117 +1,280 @@
 #[macro_use]
 mod support;
 
-/// Helper to get the 'canonical' version of a `Quat`. We define the canonical of quat `q` as:
-/// * `q`, if q.w > epsilon
-/// * `-q`, if q.w < -epsilon
-/// * `(0, 0, 0, 1)` otherwise
-/// The rationale is that q and -q represent the same rotation, and any (_, _, _, 0) represent no rotation at all.
-trait CanonicalQuat: Copy {
-    fn canonical(self) -> Self;
-}
+mod euler {
+    use glam::*;
+    use std::ops::RangeInclusive;
+
+    /// Helper to get the 'canonical' version of a `Quat`. We define the canonical of quat `q` as:
+    /// * `q`, if q.w > epsilon
+    /// * `-q`, if q.w < -epsilon
+    /// * `(0, 0, 0, 1)` otherwise
+    /// The rationale is that q and -q represent the same rotation, and any (_, _, _, 0) represent no rotation at all.
+    trait CanonicalQuat: Copy {
+        fn canonical(self) -> Self;
+    }
+
+    /// Helper to set some alternative epsilons based on the floating point type used
+    trait EulerEpsilon {
+        /// epsilon for comparing quaternion round-tripped through eulers (quat -> euler -> quat)
+        const E_EPS: f32;
+    }
+
+    impl EulerEpsilon for f32 {
+        const E_EPS: f32 = 2e-6;
+    }
+
+    impl EulerEpsilon for f64 {
+        const E_EPS: f32 = 1e-8;
+    }
+
+    fn axis_order(order: EulerRot) -> (usize, usize, usize) {
+        match order {
+            EulerRot::XYZ => (0, 1, 2),
+            EulerRot::XYX => (0, 1, 0),
+            EulerRot::XZY => (0, 2, 1),
+            EulerRot::XZX => (0, 2, 0),
+            EulerRot::YZX => (1, 2, 0),
+            EulerRot::YZY => (1, 2, 1),
+            EulerRot::YXZ => (1, 0, 2),
+            EulerRot::YXY => (1, 0, 1),
+            EulerRot::ZXY => (2, 0, 1),
+            EulerRot::ZXZ => (2, 0, 2),
+            EulerRot::ZYX => (2, 1, 0),
+            EulerRot::ZYZ => (2, 1, 2),
+            EulerRot::ZYXEx => (2, 1, 0),
+            EulerRot::XYXEx => (0, 1, 0),
+            EulerRot::YZXEx => (1, 2, 0),
+            EulerRot::XZXEx => (0, 2, 0),
+            EulerRot::XZYEx => (0, 2, 1),
+            EulerRot::YZYEx => (1, 2, 1),
+            EulerRot::ZXYEx => (2, 0, 1),
+            EulerRot::YXYEx => (1, 0, 1),
+            EulerRot::YXZEx => (1, 0, 2),
+            EulerRot::ZXZEx => (2, 0, 2),
+            EulerRot::XYZEx => (0, 1, 2),
+            EulerRot::ZYZEx => (2, 1, 2),
+        }
+    }
+
+    fn is_intrinsic(order: EulerRot) -> bool {
+        match order {
+            EulerRot::XYZ
+            | EulerRot::XYX
+            | EulerRot::XZY
+            | EulerRot::XZX
+            | EulerRot::YZX
+            | EulerRot::YZY
+            | EulerRot::YXZ
+            | EulerRot::YXY
+            | EulerRot::ZXY
+            | EulerRot::ZXZ
+            | EulerRot::ZYX
+            | EulerRot::ZYZ => true,
+            EulerRot::ZYXEx
+            | EulerRot::XYXEx
+            | EulerRot::YZXEx
+            | EulerRot::XZXEx
+            | EulerRot::XZYEx
+            | EulerRot::YZYEx
+            | EulerRot::ZXYEx
+            | EulerRot::YXYEx
+            | EulerRot::YXZEx
+            | EulerRot::ZXZEx
+            | EulerRot::XYZEx
+            | EulerRot::ZYZEx => false,
+        }
+    }
+
+    mod f32 {
+        pub fn deg_to_rad(a: i32, b: i32, c: i32) -> (f32, f32, f32) {
+            (
+                (a as f32).to_radians(),
+                (b as f32).to_radians(),
+                (c as f32).to_radians(),
+            )
+        }
+    }
+
+    mod f64 {
+        pub fn deg_to_rad(a: i32, b: i32, c: i32) -> (f64, f64, f64) {
+            (
+                (a as f64).to_radians(),
+                (b as f64).to_radians(),
+                (c as f64).to_radians(),
+            )
+        }
+    }
 
-macro_rules! impl_canonical_quat {
-    ($t:ty) => {
-        impl CanonicalQuat for $t {
-            fn canonical(self) -> Self {
-                match self {
-                    _ if self.w >= 1e-5 => self,
-                    _ if self.w <= -1e-5 => -self,
-                    _ => <$t>::from_xyzw(0.0, 0.0, 0.0, 1.0),
+    fn test_order_angles<F: Fn(EulerRot, i32, i32, i32)>(order: EulerRot, test: &F) {
+        const RANGE: RangeInclusive<i32> = -180..=180;
+        const STEP: usize = 15;
+        for i in RANGE.step_by(STEP) {
+            for j in RANGE.step_by(STEP) {
+                for k in RANGE.step_by(STEP) {
+                    test(order, i, j, k);
                 }
             }
         }
-    };
-}
+    }
 
-impl_canonical_quat!(glam::Quat);
-impl_canonical_quat!(glam::DQuat);
+    fn test_all_orders<F: Fn(EulerRot)>(test: &F) {
+        test(EulerRot::XYZ);
+        test(EulerRot::XZY);
+        test(EulerRot::YZX);
+        test(EulerRot::YXZ);
+        test(EulerRot::ZXY);
+        test(EulerRot::ZYX);
 
-/// Helper to set some alternative epsilons based on the floating point type used
-trait EulerEpsilon {
-    /// epsilon for comparing quaterions built from eulers and axis-angles
-    const Q_EPS: f32;
+        test(EulerRot::XZX);
+        test(EulerRot::XYX);
+        test(EulerRot::YXY);
+        test(EulerRot::YZY);
+        test(EulerRot::ZYZ);
+        test(EulerRot::ZXZ);
 
-    /// epsilon for comparing quaternion round-tripped through eulers (quat -> euler -> quat)
-    const E_EPS: f32;
-}
-impl EulerEpsilon for f32 {
-    const Q_EPS: f32 = 1e-5;
-    // TODO: sse2 impl passes with 1e-5 but scalar-math does not, why?
-    const E_EPS: f32 = 2e-4;
-}
+        test(EulerRot::XYZEx);
+        test(EulerRot::XZYEx);
+        test(EulerRot::YZXEx);
+        test(EulerRot::YXZEx);
+        test(EulerRot::ZXYEx);
+        test(EulerRot::ZYXEx);
 
-impl EulerEpsilon for f64 {
-    const Q_EPS: f32 = 1e-8;
-    const E_EPS: f32 = 1e-8;
-}
+        test(EulerRot::XZXEx);
+        test(EulerRot::XYXEx);
+        test(EulerRot::YXYEx);
+        test(EulerRot::YZYEx);
+        test(EulerRot::ZYZEx);
+        test(EulerRot::ZXZEx);
+    }
+
+    macro_rules! impl_quat_euler_test {
+        ($quat:ident, $t:ident) => {
+            use super::{
+                axis_order, is_intrinsic, test_all_orders, test_order_angles, $t::deg_to_rad,
+                CanonicalQuat, EulerEpsilon,
+            };
+            use glam::{$quat, EulerRot};
 
-macro_rules! impl_3axis_test {
-    ($name:ident, $t:ty, $quat:ident, $euler:path, $U:path, $V:path, $W:path, $vec:ident) => {
-        glam_test!($name, {
-            let euler = $euler;
-            assert!($U != $W); // First and last axis must be different for three axis
-            for u in (-180..=180).step_by(15) {
-                for v in (-180..=180).step_by(15) {
-                    for w in (-180..=180).step_by(15) {
-                        let u1 = (u as $t).to_radians();
-                        let v1 = (v as $t).to_radians();
-                        let w1 = (w as $t).to_radians();
-
-                        let q1: $quat = ($quat::from_axis_angle($U, u1)
-                            * $quat::from_axis_angle($V, v1)
-                            * $quat::from_axis_angle($W, w1))
-                        .normalize();
-
-                        // Test if the rotation is the expected
-                        let q2: $quat = $quat::from_euler(euler, u1, v1, w1).normalize();
-                        assert_approx_eq!(q1.canonical(), q2.canonical(), <$t>::Q_EPS);
-
-                        // Test quat reconstruction from angles
-                        let (u2, v2, w2) = q2.to_euler(euler);
-                        let q3 = $quat::from_euler(euler, u2, v2, w2).normalize();
-                        assert_approx_eq!(
-                            q2.canonical(),
-                            q3.canonical(),
-                            <$t>::E_EPS,
-                            format!(
-                                "angles {:?} -> {:?}",
-                                (u, v, w),
-                                (u2.to_degrees(), v2.to_degrees(), w2.to_degrees())
-                            )
-                        );
+            const AXIS_ANGLE: [fn($t) -> $quat; 3] = [
+                $quat::from_rotation_x,
+                $quat::from_rotation_y,
+                $quat::from_rotation_z,
+            ];
+
+            impl CanonicalQuat for $quat {
+                fn canonical(self) -> Self {
+                    match self {
+                        _ if self.w >= 1e-5 => self,
+                        _ if self.w <= -1e-5 => -self,
+                        _ => $quat::from_xyzw(0.0, 0.0, 0.0, 1.0),
                     }
                 }
             }
-        });
-    };
-}
 
-macro_rules! impl_all_quat_tests_three_axis {
-    ($t:ty, $q:ident, $v:ident) => {
-        impl_3axis_test!(test_euler_zyx, $t, $q, ER::ZYX, $v::Z, $v::Y, $v::X, $v);
-        impl_3axis_test!(test_euler_zxy, $t, $q, ER::ZXY, $v::Z, $v::X, $v::Y, $v);
-        impl_3axis_test!(test_euler_yxz, $t, $q, ER::YXZ, $v::Y, $v::X, $v::Z, $v);
-        impl_3axis_test!(test_euler_yzx, $t, $q, ER::YZX, $v::Y, $v::Z, $v::X, $v);
-        impl_3axis_test!(test_euler_xyz, $t, $q, ER::XYZ, $v::X, $v::Y, $v::Z, $v);
-        impl_3axis_test!(test_euler_xzy, $t, $q, ER::XZY, $v::X, $v::Z, $v::Y, $v);
-    };
-}
+            fn test_euler(order: EulerRot, a: i32, b: i32, c: i32) {
+                println!(
+                    "test_euler: {} {order:?} ({a}, {b}, {c})",
+                    stringify!($quat)
+                );
 
-mod euler {
-    use super::{CanonicalQuat, EulerEpsilon};
-    use glam::*;
-    type ER = EulerRot;
+                let (a, b, c) = deg_to_rad(a, b, c);
+                let m = $quat::from_euler(order, a, b, c);
+
+                let n = {
+                    let (i, j, k) = m.to_euler(order);
+                    $quat::from_euler(order, i, j, k)
+                };
+                assert_approx_eq!(m.canonical(), n.canonical(), $t::E_EPS);
+
+                let o = {
+                    let (i, j, k) = axis_order(order);
+                    if is_intrinsic(order) {
+                        AXIS_ANGLE[i](a) * AXIS_ANGLE[j](b) * AXIS_ANGLE[k](c)
+                    } else {
+                        AXIS_ANGLE[k](c) * AXIS_ANGLE[j](b) * AXIS_ANGLE[i](a)
+                    }
+                };
+                assert_approx_eq!(m.canonical(), o.canonical(), $t::E_EPS);
+            }
+
+            #[test]
+            fn test_all_euler_orders() {
+                let test = |order| test_order_angles(order, &test_euler);
+                test_all_orders(&test);
+            }
+        };
+    }
+
+    macro_rules! impl_mat_euler_test {
+        ($mat:ident, $t:ident) => {
+            use super::{
+                axis_order, is_intrinsic, test_all_orders, test_order_angles, $t::deg_to_rad,
+                EulerEpsilon,
+            };
+            use glam::{$mat, EulerRot};
+
+            const AXIS_ANGLE: [fn($t) -> $mat; 3] = [
+                $mat::from_rotation_x,
+                $mat::from_rotation_y,
+                $mat::from_rotation_z,
+            ];
+
+            fn test_euler(order: EulerRot, a: i32, b: i32, c: i32) {
+                println!("test_euler: {} {order:?} ({a}, {b}, {c})", stringify!($mat));
+
+                let (a, b, c) = deg_to_rad(a, b, c);
+                let m = $mat::from_euler(order, a, b, c);
+                let n = {
+                    let (i, j, k) = m.to_euler(order);
+                    $mat::from_euler(order, i, j, k)
+                };
+                assert_approx_eq!(m, n, $t::E_EPS);
+
+                let o = {
+                    let (i, j, k) = axis_order(order);
+                    if is_intrinsic(order) {
+                        AXIS_ANGLE[i](a) * AXIS_ANGLE[j](b) * AXIS_ANGLE[k](c)
+                    } else {
+                        AXIS_ANGLE[k](c) * AXIS_ANGLE[j](b) * AXIS_ANGLE[i](a)
+                    }
+                };
+                assert_approx_eq!(m, o, $t::E_EPS);
+            }
+
+            #[test]
+            fn test_all_euler_orders() {
+                let test = |order| test_order_angles(order, &test_euler);
+                test_all_orders(&test);
+            }
+        };
+    }
 
     mod quat {
-        use super::*;
+        impl_quat_euler_test!(Quat, f32);
+    }
 
-        impl_all_quat_tests_three_axis!(f32, Quat, Vec3);
+    mod mat3 {
+        impl_mat_euler_test!(Mat3, f32);
+    }
+
+    mod mat3a {
+        impl_mat_euler_test!(Mat3A, f32);
+    }
+
+    mod mat4 {
+        impl_mat_euler_test!(Mat4, f32);
     }
 
     mod dquat {
-        use super::*;
+        impl_quat_euler_test!(DQuat, f64);
+    }
+
+    mod dmat3 {
+        impl_mat_euler_test!(DMat3, f64);
+    }
 
-        impl_all_quat_tests_three_axis!(f64, DQuat, DVec3);
+    mod dmat4 {
+        impl_mat_euler_test!(DMat4, f64);
     }
 }
diff --git a/tests/vec2.rs b/tests/vec2.rs
index a8b4688..b574768 100644
--- a/tests/vec2.rs
+++ b/tests/vec2.rs
@@ -4,7 +4,7 @@
 mod support;
 
 macro_rules! impl_vec2_tests {
-    ($t:ident, $new:ident, $vec2:ident, $vec3:ident, $mask:ident) => {
+    ($t:ident, $new:ident, $vec2:ident, $vec3:ident, $mask:ident, $masknew:ident) => {
         glam_test!(test_const, {
             const V0: $vec2 = $vec2::splat(1 as $t);
             const V1: $vec2 = $vec2::new(1 as $t, 2 as $t);
@@ -269,6 +269,13 @@ macro_rules! impl_vec2_tests {
             assert!(b.cmpeq($vec2::splat(1 as $t)).all());
         });
 
+        glam_test!(test_mask_new, {
+            assert_eq!($mask::new(false, false), $masknew(false, false));
+            assert_eq!($mask::new(true, false), $masknew(true, false));
+            assert_eq!($mask::new(false, true), $masknew(false, true));
+            assert_eq!($mask::new(true, true), $masknew(true, true));
+        });
+
         glam_test!(test_mask_from_array_bool, {
             assert_eq!($mask::new(false, false), $mask::from([false, false]));
             assert_eq!($mask::new(true, false), $mask::from([true, false]));
@@ -522,8 +529,8 @@ macro_rules! impl_vec2_tests {
 }
 
 macro_rules! impl_vec2_signed_tests {
-    ($t:ident, $new:ident, $vec2:ident, $vec3:ident, $mask:ident) => {
-        impl_vec2_tests!($t, $new, $vec2, $vec3, $mask);
+    ($t:ident, $new:ident, $vec2:ident, $vec3:ident, $mask:ident, $masknew:ident) => {
+        impl_vec2_tests!($t, $new, $vec2, $vec3, $mask, $masknew);
 
         glam_test!(test_is_negative_bitmask, {
             assert_eq!($vec2::ZERO.is_negative_bitmask(), 0b00);
@@ -606,8 +613,8 @@ macro_rules! impl_vec2_signed_tests {
 }
 
 macro_rules! impl_vec2_signed_integer_tests {
-    ($t:ident, $new:ident, $vec2:ident, $vec3:ident, $mask:ident) => {
-        impl_vec2_signed_tests!($t, $new, $vec2, $vec3, $mask);
+    ($t:ident, $new:ident, $vec2:ident, $vec3:ident, $mask:ident, $masknew:ident) => {
+        impl_vec2_signed_tests!($t, $new, $vec2, $vec3, $mask, $masknew);
 
         glam_test!(test_signum, {
             assert_eq!($vec3::ZERO.signum(), $vec3::ZERO);
@@ -649,8 +656,8 @@ macro_rules! impl_vec2_eq_hash_tests {
 }
 
 macro_rules! impl_vec2_float_tests {
-    ($t:ident, $new:ident, $vec2:ident, $vec3:ident, $mask:ident) => {
-        impl_vec2_signed_tests!($t, $new, $vec2, $vec3, $mask);
+    ($t:ident, $new:ident, $vec2:ident, $vec3:ident, $mask:ident, $masknew:ident) => {
+        impl_vec2_signed_tests!($t, $new, $vec2, $vec3, $mask, $masknew);
         impl_vec_float_normalize_tests!($t, $vec2);
 
         glam_test!(test_vec2_nan, {
@@ -1154,7 +1161,7 @@ macro_rules! impl_vec2_bit_op_tests {
 }
 
 mod vec2 {
-    use glam::{vec2, BVec2, Vec2, Vec3};
+    use glam::{bvec2, vec2, BVec2, Vec2, Vec3};
 
     glam_test!(test_align, {
         use core::mem;
@@ -1234,11 +1241,11 @@ mod vec2 {
         assert_eq!(I64Vec2::new(1, 2), U64Vec2::new(1, 2).as_i64vec2());
     });
 
-    impl_vec2_float_tests!(f32, vec2, Vec2, Vec3, BVec2);
+    impl_vec2_float_tests!(f32, vec2, Vec2, Vec3, BVec2, bvec2);
 }
 
 mod dvec2 {
-    use glam::{dvec2, BVec2, DVec2, DVec3, IVec2, UVec2, Vec2};
+    use glam::{bvec2, dvec2, BVec2, DVec2, DVec3, IVec2, UVec2, Vec2};
 
     glam_test!(test_align, {
         use core::mem;
@@ -1257,11 +1264,11 @@ mod dvec2 {
         assert_eq!(DVec2::new(1.0, 2.0), DVec2::from(UVec2::new(1, 2)));
     });
 
-    impl_vec2_float_tests!(f64, dvec2, DVec2, DVec3, BVec2);
+    impl_vec2_float_tests!(f64, dvec2, DVec2, DVec3, BVec2, bvec2);
 }
 
 mod i16vec2 {
-    use glam::{i16vec2, BVec2, I16Vec2, I16Vec3, I64Vec2, IVec2, U16Vec2, U64Vec2, UVec2};
+    use glam::{bvec2, i16vec2, BVec2, I16Vec2, I16Vec3, I64Vec2, IVec2, U16Vec2, U64Vec2, UVec2};
 
     glam_test!(test_align, {
         use core::mem;
@@ -1393,7 +1400,7 @@ mod i16vec2 {
         );
     });
 
-    impl_vec2_signed_integer_tests!(i16, i16vec2, I16Vec2, I16Vec3, BVec2);
+    impl_vec2_signed_integer_tests!(i16, i16vec2, I16Vec2, I16Vec3, BVec2, bvec2);
     impl_vec2_eq_hash_tests!(i16, i16vec2);
 
     impl_vec2_scalar_shift_op_tests!(I16Vec2, -2, 2);
@@ -1404,7 +1411,7 @@ mod i16vec2 {
 }
 
 mod u16vec2 {
-    use glam::{u16vec2, BVec2, I16Vec2, I64Vec2, IVec2, U16Vec2, U16Vec3, U64Vec2, UVec2};
+    use glam::{bvec2, u16vec2, BVec2, I16Vec2, I64Vec2, IVec2, U16Vec2, U16Vec3, U64Vec2, UVec2};
 
     glam_test!(test_align, {
         use core::mem;
@@ -1528,7 +1535,7 @@ mod u16vec2 {
         );
     });
 
-    impl_vec2_tests!(u16, u16vec2, U16Vec2, U16Vec3, BVec2);
+    impl_vec2_tests!(u16, u16vec2, U16Vec2, U16Vec3, BVec2, bvec2);
     impl_vec2_eq_hash_tests!(u16, u16vec2);
 
     impl_vec2_scalar_shift_op_tests!(U16Vec2, 0, 2);
@@ -1539,7 +1546,7 @@ mod u16vec2 {
 }
 
 mod ivec2 {
-    use glam::{ivec2, BVec2, I16Vec2, I64Vec2, IVec2, IVec3, U16Vec2, U64Vec2, UVec2};
+    use glam::{bvec2, ivec2, BVec2, I16Vec2, I64Vec2, IVec2, IVec3, U16Vec2, U64Vec2, UVec2};
 
     glam_test!(test_align, {
         use core::mem;
@@ -1659,7 +1666,7 @@ mod ivec2 {
         );
     });
 
-    impl_vec2_signed_integer_tests!(i32, ivec2, IVec2, IVec3, BVec2);
+    impl_vec2_signed_integer_tests!(i32, ivec2, IVec2, IVec3, BVec2, bvec2);
     impl_vec2_eq_hash_tests!(i32, ivec2);
 
     impl_vec2_scalar_shift_op_tests!(IVec2, -2, 2);
@@ -1670,7 +1677,7 @@ mod ivec2 {
 }
 
 mod uvec2 {
-    use glam::{uvec2, BVec2, I16Vec2, I64Vec2, IVec2, U16Vec2, U64Vec2, UVec2, UVec3};
+    use glam::{bvec2, uvec2, BVec2, I16Vec2, I64Vec2, IVec2, U16Vec2, U64Vec2, UVec2, UVec3};
 
     glam_test!(test_align, {
         use core::mem;
@@ -1785,7 +1792,7 @@ mod uvec2 {
         );
     });
 
-    impl_vec2_tests!(u32, uvec2, UVec2, UVec3, BVec2);
+    impl_vec2_tests!(u32, uvec2, UVec2, UVec3, BVec2, bvec2);
     impl_vec2_eq_hash_tests!(u32, uvec2);
 
     impl_vec2_scalar_shift_op_tests!(UVec2, 0, 2);
@@ -1796,7 +1803,7 @@ mod uvec2 {
 }
 
 mod i64vec2 {
-    use glam::{i64vec2, BVec2, I16Vec2, I64Vec2, I64Vec3, IVec2, U16Vec2, U64Vec2, UVec2};
+    use glam::{bvec2, i64vec2, BVec2, I16Vec2, I64Vec2, I64Vec3, IVec2, U16Vec2, U64Vec2, UVec2};
 
     glam_test!(test_align, {
         use core::mem;
@@ -1851,7 +1858,7 @@ mod i64vec2 {
         );
     });
 
-    impl_vec2_signed_integer_tests!(i64, i64vec2, I64Vec2, I64Vec3, BVec2);
+    impl_vec2_signed_integer_tests!(i64, i64vec2, I64Vec2, I64Vec3, BVec2, bvec2);
     impl_vec2_eq_hash_tests!(i64, i64vec2);
 
     impl_vec2_scalar_shift_op_tests!(I64Vec2, -2, 2);
@@ -1862,7 +1869,7 @@ mod i64vec2 {
 }
 
 mod u64vec2 {
-    use glam::{u64vec2, BVec2, I16Vec2, I64Vec2, IVec2, U16Vec2, U64Vec2, U64Vec3, UVec2};
+    use glam::{bvec2, u64vec2, BVec2, I16Vec2, I64Vec2, IVec2, U16Vec2, U64Vec2, U64Vec3, UVec2};
 
     glam_test!(test_align, {
         use core::mem;
@@ -1916,7 +1923,7 @@ mod u64vec2 {
         );
     });
 
-    impl_vec2_tests!(u64, u64vec2, U64Vec2, U64Vec3, BVec2);
+    impl_vec2_tests!(u64, u64vec2, U64Vec2, U64Vec3, BVec2, bvec2);
     impl_vec2_eq_hash_tests!(u64, u64vec2);
 
     impl_vec2_scalar_shift_op_tests!(U64Vec2, 0, 2);
diff --git a/tests/vec3.rs b/tests/vec3.rs
index b5e62d3..82877ef 100644
--- a/tests/vec3.rs
+++ b/tests/vec3.rs
@@ -4,7 +4,7 @@
 mod support;
 
 macro_rules! impl_vec3_tests {
-    ($t:ident, $new:ident, $vec3:ident, $mask:ident) => {
+    ($t:ident, $new:ident, $vec3:ident, $mask:ident, $masknew:ident) => {
         glam_test!(test_const, {
             const V0: $vec3 = $vec3::splat(1 as $t);
             const V1: $vec3 = $vec3::new(1 as $t, 2 as $t, 3 as $t);
@@ -310,6 +310,18 @@ macro_rules! impl_vec3_tests {
             assert!(a.cmpeq($vec3::ONE).all());
         });
 
+        glam_test!(test_mask_new, {
+            assert_eq!(
+                $mask::new(false, false, false),
+                $masknew(false, false, false)
+            );
+            assert_eq!($mask::new(true, false, false), $masknew(true, false, false));
+            assert_eq!($mask::new(false, true, true), $masknew(false, true, true));
+            assert_eq!($mask::new(false, true, false), $masknew(false, true, false));
+            assert_eq!($mask::new(true, false, true), $masknew(true, false, true));
+            assert_eq!($mask::new(true, true, true), $masknew(true, true, true));
+        });
+
         glam_test!(test_mask_from_array_bool, {
             assert_eq!(
                 $mask::new(false, false, false),
@@ -622,8 +634,8 @@ macro_rules! impl_vec3_tests {
 }
 
 macro_rules! impl_vec3_signed_tests {
-    ($t:ident, $new:ident, $vec3:ident, $mask:ident) => {
-        impl_vec3_tests!($t, $new, $vec3, $mask);
+    ($t:ident, $new:ident, $vec3:ident, $mask:ident, $masknew:ident) => {
+        impl_vec3_tests!($t, $new, $vec3, $mask, $masknew);
 
         glam_test!(test_neg, {
             let a = $new(1 as $t, 2 as $t, 3 as $t);
@@ -716,8 +728,8 @@ macro_rules! impl_vec3_signed_tests {
 }
 
 macro_rules! impl_vec3_signed_integer_tests {
-    ($t:ident, $new:ident, $vec3:ident, $mask:ident) => {
-        impl_vec3_signed_tests!($t, $new, $vec3, $mask);
+    ($t:ident, $new:ident, $vec3:ident, $mask:ident, $masknew:ident) => {
+        impl_vec3_signed_tests!($t, $new, $vec3, $mask, $masknew);
 
         glam_test!(test_signum, {
             assert_eq!($vec3::ZERO.signum(), $vec3::ZERO);
@@ -759,8 +771,8 @@ macro_rules! impl_vec3_eq_hash_tests {
 }
 
 macro_rules! impl_vec3_float_tests {
-    ($t:ident, $new:ident, $vec3:ident, $mask:ident) => {
-        impl_vec3_signed_tests!($t, $new, $vec3, $mask);
+    ($t:ident, $new:ident, $vec3:ident, $mask:ident, $masknew:ident) => {
+        impl_vec3_signed_tests!($t, $new, $vec3, $mask, $masknew);
         impl_vec_float_normalize_tests!($t, $vec3);
 
         glam_test!(test_nan, {
@@ -1278,7 +1290,7 @@ macro_rules! impl_vec3_bit_op_tests {
 }
 
 mod vec3 {
-    use glam::{vec3, BVec3, Vec3};
+    use glam::{bvec3, vec3, BVec3, Vec3};
 
     glam_test!(test_align, {
         use std::mem;
@@ -1460,12 +1472,11 @@ mod vec3 {
         assert_eq!(Vec3A::new(1.0, 2.0, 3.0), U64Vec3::new(1, 2, 3).as_vec3a());
     });
 
-    impl_vec3_float_tests!(f32, vec3, Vec3, BVec3);
+    impl_vec3_float_tests!(f32, vec3, Vec3, BVec3, bvec3);
 }
 
 mod vec3a {
-    use glam::BVec3A;
-    use glam::{vec3a, Vec3A, Vec4};
+    use glam::{bvec3a, vec3a, BVec3A, Vec3A, Vec4};
 
     glam_test!(test_align, {
         use std::mem;
@@ -1530,11 +1541,11 @@ mod vec3a {
         assert_eq!(v2.min_element(), 2.0);
     });
 
-    impl_vec3_float_tests!(f32, vec3a, Vec3A, BVec3A);
+    impl_vec3_float_tests!(f32, vec3a, Vec3A, BVec3A, bvec3a);
 }
 
 mod dvec3 {
-    use glam::{dvec3, BVec3, DVec3, IVec3, UVec3, Vec3};
+    use glam::{bvec3, dvec3, BVec3, DVec3, IVec3, UVec3, Vec3};
 
     glam_test!(test_align, {
         use std::mem;
@@ -1553,11 +1564,11 @@ mod dvec3 {
         assert_eq!(DVec3::new(1.0, 2.0, 3.0), DVec3::from(UVec3::new(1, 2, 3)));
     });
 
-    impl_vec3_float_tests!(f64, dvec3, DVec3, BVec3);
+    impl_vec3_float_tests!(f64, dvec3, DVec3, BVec3, bvec3);
 }
 
 mod i16vec3 {
-    use glam::{i16vec3, BVec3, I16Vec3, I64Vec3, IVec3, U16Vec3, U64Vec3, UVec3};
+    use glam::{bvec3, i16vec3, BVec3, I16Vec3, I64Vec3, IVec3, U16Vec3, U64Vec3, UVec3};
 
     glam_test!(test_align, {
         use std::mem;
@@ -1693,7 +1704,7 @@ mod i16vec3 {
         );
     });
 
-    impl_vec3_signed_integer_tests!(i16, i16vec3, I16Vec3, BVec3);
+    impl_vec3_signed_integer_tests!(i16, i16vec3, I16Vec3, BVec3, bvec3);
     impl_vec3_eq_hash_tests!(i16, i16vec3);
 
     impl_vec3_scalar_shift_op_tests!(I16Vec3, -2, 2);
@@ -1704,7 +1715,7 @@ mod i16vec3 {
 }
 
 mod u16vec3 {
-    use glam::{u16vec3, BVec3, I16Vec3, I64Vec3, IVec3, U16Vec3, U64Vec3, UVec3};
+    use glam::{bvec3, u16vec3, BVec3, I16Vec3, I64Vec3, IVec3, U16Vec3, U64Vec3, UVec3};
 
     glam_test!(test_align, {
         use std::mem;
@@ -1832,7 +1843,7 @@ mod u16vec3 {
         );
     });
 
-    impl_vec3_tests!(u16, u16vec3, U16Vec3, BVec3);
+    impl_vec3_tests!(u16, u16vec3, U16Vec3, BVec3, bvec3);
     impl_vec3_eq_hash_tests!(u16, u16vec3);
 
     impl_vec3_scalar_shift_op_tests!(U16Vec3, 0, 2);
@@ -1843,7 +1854,7 @@ mod u16vec3 {
 }
 
 mod ivec3 {
-    use glam::{ivec3, BVec3, I16Vec3, I64Vec3, IVec3, U16Vec3, U64Vec3, UVec3};
+    use glam::{bvec3, ivec3, BVec3, I16Vec3, I64Vec3, IVec3, U16Vec3, U64Vec3, UVec3};
 
     glam_test!(test_align, {
         use std::mem;
@@ -1966,7 +1977,7 @@ mod ivec3 {
         );
     });
 
-    impl_vec3_signed_integer_tests!(i32, ivec3, IVec3, BVec3);
+    impl_vec3_signed_integer_tests!(i32, ivec3, IVec3, BVec3, bvec3);
     impl_vec3_eq_hash_tests!(i32, ivec3);
 
     impl_vec3_scalar_shift_op_tests!(IVec3, -2, 2);
@@ -1977,7 +1988,7 @@ mod ivec3 {
 }
 
 mod uvec3 {
-    use glam::{uvec3, BVec3, I16Vec3, I64Vec3, IVec3, U16Vec3, U64Vec3, UVec3};
+    use glam::{bvec3, uvec3, BVec3, I16Vec3, I64Vec3, IVec3, U16Vec3, U64Vec3, UVec3};
 
     glam_test!(test_align, {
         use std::mem;
@@ -2097,7 +2108,7 @@ mod uvec3 {
         );
     });
 
-    impl_vec3_tests!(u32, uvec3, UVec3, BVec3);
+    impl_vec3_tests!(u32, uvec3, UVec3, BVec3, bvec3);
     impl_vec3_eq_hash_tests!(u32, uvec3);
 
     impl_vec3_scalar_shift_op_tests!(UVec3, 0, 2);
@@ -2108,7 +2119,7 @@ mod uvec3 {
 }
 
 mod i64vec3 {
-    use glam::{i64vec3, BVec3, I16Vec3, I64Vec3, IVec3, U16Vec3, U64Vec3, UVec3};
+    use glam::{bvec3, i64vec3, BVec3, I16Vec3, I64Vec3, IVec3, U16Vec3, U64Vec3, UVec3};
 
     glam_test!(test_align, {
         use std::mem;
@@ -2163,7 +2174,7 @@ mod i64vec3 {
         );
     });
 
-    impl_vec3_signed_integer_tests!(i64, i64vec3, I64Vec3, BVec3);
+    impl_vec3_signed_integer_tests!(i64, i64vec3, I64Vec3, BVec3, bvec3);
     impl_vec3_eq_hash_tests!(i64, i64vec3);
 
     impl_vec3_scalar_shift_op_tests!(I64Vec3, -2, 2);
@@ -2174,7 +2185,7 @@ mod i64vec3 {
 }
 
 mod u64vec3 {
-    use glam::{u64vec3, BVec3, I16Vec3, I64Vec3, IVec3, U16Vec3, U64Vec3, UVec3};
+    use glam::{bvec3, u64vec3, BVec3, I16Vec3, I64Vec3, IVec3, U16Vec3, U64Vec3, UVec3};
 
     glam_test!(test_align, {
         use std::mem;
@@ -2228,7 +2239,7 @@ mod u64vec3 {
         );
     });
 
-    impl_vec3_tests!(u64, u64vec3, U64Vec3, BVec3);
+    impl_vec3_tests!(u64, u64vec3, U64Vec3, BVec3, bvec3);
     impl_vec3_eq_hash_tests!(u64, u64vec3);
 
     impl_vec3_scalar_shift_op_tests!(U64Vec3, 0, 2);
diff --git a/tests/vec4.rs b/tests/vec4.rs
index b0b0990..47599e1 100644
--- a/tests/vec4.rs
+++ b/tests/vec4.rs
@@ -4,7 +4,7 @@
 mod support;
 
 macro_rules! impl_vec4_tests {
-    ($t:ident, $new:ident, $vec4:ident, $vec3:ident, $vec2:ident, $mask:ident) => {
+    ($t:ident, $new:ident, $vec4:ident, $vec3:ident, $vec2:ident, $mask:ident, $masknew:ident) => {
         glam_test!(test_const, {
             const V0: $vec4 = $vec4::splat(1 as $t);
             const V1: $vec4 = $vec4::new(1 as $t, 2 as $t, 3 as $t, 4 as $t);
@@ -400,6 +400,33 @@ macro_rules! impl_vec4_tests {
             should_panic!({ $vec4::from_slice(&[0 as $t; 3]) });
         });
 
+        glam_test!(test_mask_new, {
+            assert_eq!(
+                $mask::new(false, false, false, false),
+                $masknew(false, false, false, false)
+            );
+            assert_eq!(
+                $mask::new(false, false, true, true),
+                $masknew(false, false, true, true)
+            );
+            assert_eq!(
+                $mask::new(true, true, false, false),
+                $masknew(true, true, false, false)
+            );
+            assert_eq!(
+                $mask::new(false, true, false, true),
+                $masknew(false, true, false, true)
+            );
+            assert_eq!(
+                $mask::new(true, false, true, false),
+                $masknew(true, false, true, false)
+            );
+            assert_eq!(
+                $mask::new(true, true, true, true),
+                $masknew(true, true, true, true)
+            );
+        });
+
         glam_test!(test_mask_from_array_bool, {
             assert_eq!(
                 $mask::new(false, false, false, false),
@@ -426,6 +453,7 @@ macro_rules! impl_vec4_tests {
                 $mask::from([true, true, true, true])
             );
         });
+
         glam_test!(test_mask_into_array_u32, {
             assert_eq!(
                 Into::<[u32; 4]>::into($mask::new(false, false, false, false)),
@@ -722,8 +750,8 @@ macro_rules! impl_vec4_tests {
 }
 
 macro_rules! impl_vec4_signed_tests {
-    ($t:ident, $new:ident, $vec4:ident, $vec3:ident, $vec2:ident, $mask:ident) => {
-        impl_vec4_tests!($t, $new, $vec4, $vec3, $vec2, $mask);
+    ($t:ident, $new:ident, $vec4:ident, $vec3:ident, $vec2:ident, $mask:ident, $masknew:ident) => {
+        impl_vec4_tests!($t, $new, $vec4, $vec3, $vec2, $mask, $masknew);
 
         glam_test!(test_neg, {
             let a = $new(1 as $t, 2 as $t, 3 as $t, 4 as $t);
@@ -818,8 +846,8 @@ macro_rules! impl_vec4_signed_tests {
 }
 
 macro_rules! impl_vec4_signed_integer_tests {
-    ($t:ident, $new:ident, $vec4:ident, $vec3:ident, $vec2:ident, $mask:ident) => {
-        impl_vec4_signed_tests!($t, $new, $vec4, $vec3, $vec2, $mask);
+    ($t:ident, $new:ident, $vec4:ident, $vec3:ident, $vec2:ident, $mask:ident, $masknew:ident) => {
+        impl_vec4_signed_tests!($t, $new, $vec4, $vec3, $vec2, $mask, $masknew);
 
         glam_test!(test_signum, {
             assert_eq!($vec4::ZERO.signum(), $vec4::ZERO);
@@ -861,8 +889,8 @@ macro_rules! impl_vec4_eq_hash_tests {
 }
 
 macro_rules! impl_vec4_float_tests {
-    ($t:ident, $new:ident, $vec4:ident, $vec3:ident, $vec2:ident, $mask:ident) => {
-        impl_vec4_signed_tests!($t, $new, $vec4, $vec3, $vec2, $mask);
+    ($t:ident, $new:ident, $vec4:ident, $vec3:ident, $vec2:ident, $mask:ident, $masknew:ident) => {
+        impl_vec4_signed_tests!($t, $new, $vec4, $vec3, $vec2, $mask, $masknew);
         impl_vec_float_normalize_tests!($t, $vec4);
 
         glam_test!(test_vec4_nan, {
@@ -1394,9 +1422,9 @@ macro_rules! impl_vec4_bit_op_tests {
 
 mod vec4 {
     #[cfg(feature = "scalar-math")]
-    use glam::BVec4;
+    use glam::{bvec4, BVec4};
     #[cfg(not(feature = "scalar-math"))]
-    use glam::BVec4A;
+    use glam::{bvec4a, BVec4A};
     use glam::{vec4, Vec2, Vec3, Vec4};
 
     glam_test!(test_align, {
@@ -1679,14 +1707,14 @@ mod vec4 {
     });
 
     #[cfg(not(feature = "scalar-math"))]
-    impl_vec4_float_tests!(f32, vec4, Vec4, Vec3, Vec2, BVec4A);
+    impl_vec4_float_tests!(f32, vec4, Vec4, Vec3, Vec2, BVec4A, bvec4a);
 
     #[cfg(feature = "scalar-math")]
-    impl_vec4_float_tests!(f32, vec4, Vec4, Vec3, Vec2, BVec4);
+    impl_vec4_float_tests!(f32, vec4, Vec4, Vec3, Vec2, BVec4, bvec4);
 }
 
 mod dvec4 {
-    use glam::{dvec4, BVec4, DVec2, DVec3, DVec4, IVec4, UVec4, Vec4};
+    use glam::{bvec4, dvec4, BVec4, DVec2, DVec3, DVec4, IVec4, UVec4, Vec4};
 
     glam_test!(test_align, {
         use std::mem;
@@ -1714,12 +1742,12 @@ mod dvec4 {
         );
     });
 
-    impl_vec4_float_tests!(f64, dvec4, DVec4, DVec3, DVec2, BVec4);
+    impl_vec4_float_tests!(f64, dvec4, DVec4, DVec3, DVec2, BVec4, bvec4);
 }
 
 mod i16vec4 {
     use glam::{
-        i16vec4, BVec4, I16Vec2, I16Vec3, I16Vec4, I64Vec4, IVec4, U16Vec4, U64Vec4, UVec4,
+        bvec4, i16vec4, BVec4, I16Vec2, I16Vec3, I16Vec4, I64Vec4, IVec4, U16Vec4, U64Vec4, UVec4,
     };
 
     glam_test!(test_align, {
@@ -1866,7 +1894,7 @@ mod i16vec4 {
         );
     });
 
-    impl_vec4_signed_integer_tests!(i16, i16vec4, I16Vec4, I16Vec3, I16Vec2, BVec4);
+    impl_vec4_signed_integer_tests!(i16, i16vec4, I16Vec4, I16Vec3, I16Vec2, BVec4, bvec4);
     impl_vec4_eq_hash_tests!(i16, i16vec4);
 
     impl_vec4_scalar_shift_op_tests!(I16Vec4, -2, 2);
@@ -1878,7 +1906,7 @@ mod i16vec4 {
 
 mod u16vec4 {
     use glam::{
-        u16vec4, BVec4, I16Vec4, I64Vec4, IVec4, U16Vec2, U16Vec3, U16Vec4, U64Vec4, UVec4,
+        bvec4, u16vec4, BVec4, I16Vec4, I64Vec4, IVec4, U16Vec2, U16Vec3, U16Vec4, U64Vec4, UVec4,
     };
 
     glam_test!(test_align, {
@@ -2024,7 +2052,7 @@ mod u16vec4 {
         );
     });
 
-    impl_vec4_tests!(u16, u16vec4, U16Vec4, U16Vec3, U16Vec2, BVec4);
+    impl_vec4_tests!(u16, u16vec4, U16Vec4, U16Vec3, U16Vec2, BVec4, bvec4);
     impl_vec4_eq_hash_tests!(u16, u16vec4);
 
     impl_vec4_scalar_shift_op_tests!(U16Vec4, 0, 2);
@@ -2035,7 +2063,9 @@ mod u16vec4 {
 }
 
 mod ivec4 {
-    use glam::{ivec4, BVec4, I16Vec4, I64Vec4, IVec2, IVec3, IVec4, U16Vec4, U64Vec4, UVec4};
+    use glam::{
+        bvec4, ivec4, BVec4, I16Vec4, I64Vec4, IVec2, IVec3, IVec4, U16Vec4, U64Vec4, UVec4,
+    };
 
     glam_test!(test_align, {
         use std::mem;
@@ -2174,7 +2204,7 @@ mod ivec4 {
         );
     });
 
-    impl_vec4_signed_integer_tests!(i32, ivec4, IVec4, IVec3, IVec2, BVec4);
+    impl_vec4_signed_integer_tests!(i32, ivec4, IVec4, IVec3, IVec2, BVec4, bvec4);
     impl_vec4_eq_hash_tests!(i32, ivec4);
 
     impl_vec4_scalar_shift_op_tests!(IVec4, -2, 2);
@@ -2185,7 +2215,9 @@ mod ivec4 {
 }
 
 mod uvec4 {
-    use glam::{uvec4, BVec4, I16Vec4, I64Vec4, IVec4, U16Vec4, U64Vec4, UVec2, UVec3, UVec4};
+    use glam::{
+        bvec4, uvec4, BVec4, I16Vec4, I64Vec4, IVec4, U16Vec4, U64Vec4, UVec2, UVec3, UVec4,
+    };
 
     glam_test!(test_align, {
         use std::mem;
@@ -2318,7 +2350,7 @@ mod uvec4 {
         );
     });
 
-    impl_vec4_tests!(u32, uvec4, UVec4, UVec3, UVec2, BVec4);
+    impl_vec4_tests!(u32, uvec4, UVec4, UVec3, UVec2, BVec4, bvec4);
     impl_vec4_eq_hash_tests!(u32, uvec4);
 
     impl_vec4_scalar_shift_op_tests!(UVec4, 0, 2);
@@ -2330,7 +2362,7 @@ mod uvec4 {
 
 mod i64vec4 {
     use glam::{
-        i64vec4, BVec4, I16Vec4, I64Vec2, I64Vec3, I64Vec4, IVec4, U16Vec4, U64Vec4, UVec4,
+        bvec4, i64vec4, BVec4, I16Vec4, I64Vec2, I64Vec3, I64Vec4, IVec4, U16Vec4, U64Vec4, UVec4,
     };
 
     glam_test!(test_align, {
@@ -2404,7 +2436,7 @@ mod i64vec4 {
         );
     });
 
-    impl_vec4_signed_integer_tests!(i64, i64vec4, I64Vec4, I64Vec3, I64Vec2, BVec4);
+    impl_vec4_signed_integer_tests!(i64, i64vec4, I64Vec4, I64Vec3, I64Vec2, BVec4, bvec4);
     impl_vec4_eq_hash_tests!(i64, i64vec4);
 
     impl_vec4_scalar_shift_op_tests!(I64Vec4, -2, 2);
@@ -2416,7 +2448,7 @@ mod i64vec4 {
 
 mod u64vec4 {
     use glam::{
-        u64vec4, BVec4, I16Vec4, I64Vec4, IVec4, U16Vec4, U64Vec2, U64Vec3, U64Vec4, UVec4,
+        bvec4, u64vec4, BVec4, I16Vec4, I64Vec4, IVec4, U16Vec4, U64Vec2, U64Vec3, U64Vec4, UVec4,
     };
 
     glam_test!(test_align, {
@@ -2485,7 +2517,7 @@ mod u64vec4 {
         );
     });
 
-    impl_vec4_tests!(u64, u64vec4, U64Vec4, U64Vec3, U64Vec2, BVec4);
+    impl_vec4_tests!(u64, u64vec4, U64Vec4, U64Vec3, U64Vec2, BVec4, bvec4);
     impl_vec4_eq_hash_tests!(u64, u64vec4);
 
     impl_vec4_scalar_shift_op_tests!(U64Vec4, 0, 2);