1use crate::{Mat4, Quat, asin, atan2, clamp, cos, sin};
2
3#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub enum RotationOrder {
7 #[default]
9 XYZ,
10 YXZ,
12 ZXY,
14 ZYX,
16 YZX,
18 XZY,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct Euler {
26 pub x: f32,
28 pub y: f32,
30 pub z: f32,
32 pub order: RotationOrder,
34}
35
36impl Euler {
37 #[inline]
39 pub const fn new(x: f32, y: f32, z: f32, order: RotationOrder) -> Self {
40 Self { x, y, z, order }
41 }
42
43 #[inline]
45 pub fn from_quat(q: Quat, order: RotationOrder) -> Self {
46 Self::from_mat4(q.to_mat4(), order)
47 }
48
49 pub fn from_mat4(matrix: Mat4, order: RotationOrder) -> Self {
51 let m11 = matrix.get(0, 0);
52 let m12 = matrix.get(0, 1);
53 let m13 = matrix.get(0, 2);
54 let m21 = matrix.get(1, 0);
55 let m22 = matrix.get(1, 1);
56 let m23 = matrix.get(1, 2);
57 let m31 = matrix.get(2, 0);
58 let m32 = matrix.get(2, 1);
59 let m33 = matrix.get(2, 2);
60
61 let (x, y, z) = match order {
62 RotationOrder::XYZ => {
63 let y = asin(clamp(m13, -1.0, 1.0));
64 if m13.abs() < 0.999_999_9 {
65 (atan2(-m23, m33), y, atan2(-m12, m11))
66 } else {
67 (atan2(m32, m22), y, 0.0)
68 }
69 }
70 RotationOrder::YXZ => {
71 let x = asin(-clamp(m23, -1.0, 1.0));
72 if m23.abs() < 0.999_999_9 {
73 (x, atan2(m13, m33), atan2(m21, m22))
74 } else {
75 (x, atan2(-m31, m11), 0.0)
76 }
77 }
78 RotationOrder::ZXY => {
79 let x = asin(clamp(m32, -1.0, 1.0));
80 if m32.abs() < 0.999_999_9 {
81 (x, atan2(-m31, m33), atan2(-m12, m22))
82 } else {
83 (x, 0.0, atan2(m21, m11))
84 }
85 }
86 RotationOrder::ZYX => {
87 let y = asin(-clamp(m31, -1.0, 1.0));
88 if m31.abs() < 0.999_999_9 {
89 (atan2(m32, m33), y, atan2(m21, m11))
90 } else {
91 (0.0, y, atan2(-m12, m22))
92 }
93 }
94 RotationOrder::YZX => {
95 let z = asin(clamp(m21, -1.0, 1.0));
96 if m21.abs() < 0.999_999_9 {
97 (atan2(-m23, m22), atan2(-m31, m11), z)
98 } else {
99 (0.0, atan2(m13, m33), z)
100 }
101 }
102 RotationOrder::XZY => {
103 let z = asin(-clamp(m12, -1.0, 1.0));
104 if m12.abs() < 0.999_999_9 {
105 (atan2(m32, m22), atan2(m13, m11), z)
106 } else {
107 (atan2(-m23, m33), 0.0, z)
108 }
109 }
110 };
111
112 Self::new(x, y, z, order)
113 }
114
115 pub fn to_quat(self) -> Quat {
117 let c1 = cos(self.x * 0.5);
118 let c2 = cos(self.y * 0.5);
119 let c3 = cos(self.z * 0.5);
120 let s1 = sin(self.x * 0.5);
121 let s2 = sin(self.y * 0.5);
122 let s3 = sin(self.z * 0.5);
123
124 match self.order {
125 RotationOrder::XYZ => Quat::new(
126 s1 * c2 * c3 + c1 * s2 * s3,
127 c1 * s2 * c3 - s1 * c2 * s3,
128 c1 * c2 * s3 + s1 * s2 * c3,
129 c1 * c2 * c3 - s1 * s2 * s3,
130 ),
131 RotationOrder::YXZ => Quat::new(
132 s1 * c2 * c3 + c1 * s2 * s3,
133 c1 * s2 * c3 - s1 * c2 * s3,
134 c1 * c2 * s3 - s1 * s2 * c3,
135 c1 * c2 * c3 + s1 * s2 * s3,
136 ),
137 RotationOrder::ZXY => Quat::new(
138 s1 * c2 * c3 - c1 * s2 * s3,
139 c1 * s2 * c3 + s1 * c2 * s3,
140 c1 * c2 * s3 + s1 * s2 * c3,
141 c1 * c2 * c3 - s1 * s2 * s3,
142 ),
143 RotationOrder::ZYX => Quat::new(
144 s1 * c2 * c3 - c1 * s2 * s3,
145 c1 * s2 * c3 + s1 * c2 * s3,
146 c1 * c2 * s3 - s1 * s2 * c3,
147 c1 * c2 * c3 + s1 * s2 * s3,
148 ),
149 RotationOrder::YZX => Quat::new(
150 s1 * c2 * c3 + c1 * s2 * s3,
151 c1 * s2 * c3 + s1 * c2 * s3,
152 c1 * c2 * s3 - s1 * s2 * c3,
153 c1 * c2 * c3 - s1 * s2 * s3,
154 ),
155 RotationOrder::XZY => Quat::new(
156 s1 * c2 * c3 - c1 * s2 * s3,
157 c1 * s2 * c3 - s1 * c2 * s3,
158 c1 * c2 * s3 + s1 * s2 * c3,
159 c1 * c2 * c3 + s1 * s2 * s3,
160 ),
161 }
162 .normalize()
163 }
164}
165
166impl Default for Euler {
167 #[inline]
168 fn default() -> Self {
169 Self::new(0.0, 0.0, 0.0, RotationOrder::XYZ)
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use crate::{Vec3, assert_close};
177
178 #[test]
179 fn xyz_round_trips_through_quat() {
180 let euler = Euler::new(0.3, 0.4, 0.5, RotationOrder::XYZ);
181 let out = Euler::from_quat(euler.to_quat(), RotationOrder::XYZ);
182 assert_close(out.x, euler.x);
183 assert_close(out.y, euler.y);
184 assert_close(out.z, euler.z);
185 }
186
187 #[test]
188 fn euler_rotates_vector() {
189 let q = Euler::new(0.0, core::f32::consts::FRAC_PI_2, 0.0, RotationOrder::XYZ).to_quat();
190 let out = q.mul_vec3(Vec3::X);
191 assert_close(out.z, -1.0);
192 }
193}