oxihuman_core/
quaternion_math.rs1#![allow(dead_code)]
4
5use std::f32::consts::PI;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct QuatMath {
12 pub x: f32,
13 pub y: f32,
14 pub z: f32,
15 pub w: f32,
16}
17
18pub fn quat_identity() -> QuatMath {
20 QuatMath {
21 x: 0.0,
22 y: 0.0,
23 z: 0.0,
24 w: 1.0,
25 }
26}
27
28pub fn quat_mul(a: QuatMath, b: QuatMath) -> QuatMath {
30 QuatMath {
31 x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
32 y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
33 z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
34 w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
35 }
36}
37
38pub fn quat_conjugate(q: QuatMath) -> QuatMath {
40 QuatMath {
41 x: -q.x,
42 y: -q.y,
43 z: -q.z,
44 w: q.w,
45 }
46}
47
48pub fn quat_norm_sq(q: QuatMath) -> f32 {
50 q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w
51}
52
53pub fn quat_normalize(q: QuatMath) -> QuatMath {
55 let len = quat_norm_sq(q).sqrt();
56 if len < 1e-12 {
57 return quat_identity();
58 }
59 QuatMath {
60 x: q.x / len,
61 y: q.y / len,
62 z: q.z / len,
63 w: q.w / len,
64 }
65}
66
67pub fn quat_from_axis_angle(axis: [f32; 3], angle_rad: f32) -> QuatMath {
69 let half = angle_rad * 0.5;
70 let s = half.sin();
71 QuatMath {
72 x: axis[0] * s,
73 y: axis[1] * s,
74 z: axis[2] * s,
75 w: half.cos(),
76 }
77}
78
79pub fn quat_rotate_vec3(q: QuatMath, v: [f32; 3]) -> [f32; 3] {
81 let vq = QuatMath {
82 x: v[0],
83 y: v[1],
84 z: v[2],
85 w: 0.0,
86 };
87 let res = quat_mul(quat_mul(q, vq), quat_conjugate(q));
88 [res.x, res.y, res.z]
89}
90
91pub fn quat_slerp(a: QuatMath, b: QuatMath, t: f32) -> QuatMath {
93 let mut dot = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
94 let b = if dot < 0.0 {
95 dot = -dot;
96 QuatMath {
97 x: -b.x,
98 y: -b.y,
99 z: -b.z,
100 w: -b.w,
101 }
102 } else {
103 b
104 };
105 if dot > 0.9995 {
106 return quat_normalize(QuatMath {
107 x: a.x + t * (b.x - a.x),
108 y: a.y + t * (b.y - a.y),
109 z: a.z + t * (b.z - a.z),
110 w: a.w + t * (b.w - a.w),
111 });
112 }
113 let theta_0 = dot.acos();
114 let theta = theta_0 * t;
115 let sin_theta = theta.sin();
116 let sin_theta_0 = theta_0.sin();
117 let s0 = (theta_0 - theta).sin() / sin_theta_0;
118 let s1 = sin_theta / sin_theta_0;
119 QuatMath {
120 x: s0 * a.x + s1 * b.x,
121 y: s0 * a.y + s1 * b.y,
122 z: s0 * a.z + s1 * b.z,
123 w: s0 * a.w + s1 * b.w,
124 }
125}
126
127pub fn quat_to_euler(q: QuatMath) -> [f32; 3] {
129 let sinr_cosp = 2.0 * (q.w * q.x + q.y * q.z);
130 let cosr_cosp = 1.0 - 2.0 * (q.x * q.x + q.y * q.y);
131 let roll = sinr_cosp.atan2(cosr_cosp);
132 let sinp = 2.0 * (q.w * q.y - q.z * q.x);
133 let pitch = if sinp.abs() >= 1.0 {
134 sinp.signum() * PI / 2.0
135 } else {
136 sinp.asin()
137 };
138 let siny_cosp = 2.0 * (q.w * q.z + q.x * q.y);
139 let cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z);
140 let yaw = siny_cosp.atan2(cosy_cosp);
141 [roll, pitch, yaw]
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use std::f32::consts::FRAC_PI_2;
148
149 const EPS: f32 = 1e-5;
150
151 #[test]
152 fn test_identity_mul() {
153 let id = quat_identity();
155 let r = quat_mul(id, id);
156 assert!((r.w - 1.0).abs() < EPS);
157 assert!(r.x.abs() < EPS);
158 }
159
160 #[test]
161 fn test_normalize_identity() {
162 let n = quat_normalize(quat_identity());
164 assert!((n.w - 1.0).abs() < EPS);
165 }
166
167 #[test]
168 fn test_conjugate_reverses_rotation() {
169 let q = quat_from_axis_angle([0.0, 0.0, 1.0], FRAC_PI_2);
171 let r = quat_mul(q, quat_conjugate(q));
172 assert!((r.w - 1.0).abs() < EPS);
173 assert!(r.x.abs() < EPS);
174 assert!(r.y.abs() < EPS);
175 assert!(r.z.abs() < EPS);
176 }
177
178 #[test]
179 fn test_from_axis_angle_z90() {
180 let q = quat_from_axis_angle([0.0, 0.0, 1.0], FRAC_PI_2);
182 let v = quat_rotate_vec3(q, [1.0, 0.0, 0.0]);
183 assert!(v[0].abs() < 1e-4);
184 assert!((v[1] - 1.0).abs() < 1e-4);
185 }
186
187 #[test]
188 fn test_slerp_endpoints() {
189 let a = quat_identity();
191 let b = quat_from_axis_angle([0.0, 1.0, 0.0], FRAC_PI_2);
192 let r0 = quat_slerp(a, b, 0.0);
193 assert!((r0.w - a.w).abs() < EPS);
194 let r1 = quat_slerp(a, b, 1.0);
195 assert!((r1.w - b.w).abs() < EPS);
196 }
197
198 #[test]
199 fn test_slerp_midpoint_norm() {
200 let a = quat_identity();
202 let b = quat_from_axis_angle([1.0, 0.0, 0.0], FRAC_PI_2);
203 let m = quat_slerp(a, b, 0.5);
204 let ns = quat_norm_sq(m).sqrt();
205 assert!((ns - 1.0).abs() < EPS);
206 }
207
208 #[test]
209 fn test_to_euler_zero() {
210 let e = quat_to_euler(quat_identity());
212 assert!(e[0].abs() < EPS);
213 assert!(e[1].abs() < EPS);
214 assert!(e[2].abs() < EPS);
215 }
216
217 #[test]
218 fn test_norm_sq_identity() {
219 let ns = quat_norm_sq(quat_identity());
221 assert!((ns - 1.0).abs() < EPS);
222 }
223
224 #[test]
225 fn test_rotate_identity_no_change() {
226 let q = quat_identity();
228 let v = quat_rotate_vec3(q, [3.0, 4.0, 5.0]);
229 assert!((v[0] - 3.0).abs() < EPS);
230 assert!((v[1] - 4.0).abs() < EPS);
231 assert!((v[2] - 5.0).abs() < EPS);
232 }
233}