1use float_cmp::{ApproxEq, F64Margin};
2use nalgebra::{Quaternion, UnitQuaternion, UnitVector3, Vector3};
3
4const ANGLE_ACCEPTANCE: f64 = 0.0001;
5
6#[repr(C)]
7#[derive(Clone, Copy, Debug)]
8pub struct EulerAngles {
9 pub roll: f64,
10 pub pitch: f64,
11 pub yaw: f64,
12}
13
14impl EulerAngles {
15 pub fn new(roll: f64, pitch: f64, yaw: f64) -> Self {
16 EulerAngles { roll, pitch, yaw }
17 }
18
19 pub fn from_quaternion(quat: &Quaternion<f64>) -> Self {
23 let norm_quat = quat.normalize();
25
26 let yaw_sin_pitch_cos: f64 =
28 2.0 * ((norm_quat.w * norm_quat.k) + (norm_quat.i * norm_quat.j));
29 let yaw_cos_pitch_cos: f64 =
30 1.0 - 2.0 * ((norm_quat.j * norm_quat.j) + (norm_quat.k * norm_quat.k));
31 let yaw = yaw_sin_pitch_cos.atan2(yaw_cos_pitch_cos);
32
33 let pitch_sin: f64 = 2.0 * ((norm_quat.w * norm_quat.j) - (norm_quat.k * norm_quat.i));
35 let pitch: f64;
36 let roll: f64;
37 if pitch_sin.abs() >= 1.0 {
40 pitch = (std::f64::consts::PI / 2.0).copysign(pitch_sin);
41 roll = (2.0 * norm_quat.i.atan2(norm_quat.w)) + yaw.copysign(pitch_sin);
42 } else {
43 pitch = pitch_sin.asin();
44 let roll_sin_pitch_cos =
45 2.0 * ((norm_quat.w * norm_quat.i) + (norm_quat.j * norm_quat.k));
46 let roll_cos_pitch_cos =
47 1.0 - 2.0 * ((norm_quat.i * norm_quat.i) + (norm_quat.j * norm_quat.j));
48 roll = roll_sin_pitch_cos.atan2(roll_cos_pitch_cos);
49 }
50
51 EulerAngles { roll, pitch, yaw }
52 }
53}
54
55impl From<Quaternion<f64>> for EulerAngles {
56 fn from(quat: Quaternion<f64>) -> Self {
57 let norm_quat = quat.normalize();
59
60 let yaw_sin_pitch_cos: f64 =
62 2.0 * ((norm_quat.w * norm_quat.k) + (norm_quat.i * norm_quat.j));
63 let yaw_cos_pitch_cos: f64 =
64 1.0 - 2.0 * ((norm_quat.j * norm_quat.j) + (norm_quat.k * norm_quat.k));
65 let yaw = yaw_sin_pitch_cos.atan2(yaw_cos_pitch_cos);
66
67 let pitch_sin: f64 = 2.0 * ((norm_quat.w * norm_quat.j) - (norm_quat.k * norm_quat.i));
69 let pitch: f64;
70 let roll: f64;
71 if pitch_sin.abs() >= 1.0 {
74 pitch = (std::f64::consts::PI / 2.0).copysign(pitch_sin);
75 roll = (2.0 * norm_quat.i.atan2(norm_quat.w)) + yaw.copysign(pitch_sin);
76 } else {
77 pitch = pitch_sin.asin();
78 let roll_sin_pitch_cos =
79 2.0 * ((norm_quat.w * norm_quat.i) + (norm_quat.j * norm_quat.k));
80 let roll_cos_pitch_cos =
81 1.0 - 2.0 * ((norm_quat.i * norm_quat.i) + (norm_quat.j * norm_quat.j));
82 roll = roll_sin_pitch_cos.atan2(roll_cos_pitch_cos);
83 }
84
85 Self { roll, pitch, yaw }
86 }
87}
88
89#[repr(C)]
90#[derive(Clone, Copy, Debug)]
91pub struct AxisAngle {
92 pub axis: Vector3<f64>,
93 pub theta: f64,
94}
95
96impl AxisAngle {
97 pub fn new(x: f64, y: f64, z: f64, theta: f64) -> Self {
98 AxisAngle {
99 axis: Vector3::new(x, y, z),
100 theta,
101 }
102 }
103}
104
105impl TryFrom<Quaternion<f64>> for AxisAngle {
106 type Error = ();
107
108 fn try_from(quat: Quaternion<f64>) -> Result<Self, Self::Error> {
109 let unit_quat = UnitQuaternion::from_quaternion(quat);
110 let axis_opt = unit_quat.axis();
111 let angle = unit_quat.angle();
112 match axis_opt {
113 Some(value) => Ok(Self::new(value[0], value[1], value[2], angle)),
114 None => Err(()),
115 }
116 }
117}
118
119#[repr(C)]
120#[derive(Clone, Copy, Debug)]
121pub struct OrientationVector {
122 pub o_vector: UnitVector3<f64>,
123 pub theta: f64,
124}
125
126impl OrientationVector {
127 pub fn new(o_x: f64, o_y: f64, o_z: f64, theta: f64) -> Self {
128 let o_vector = UnitVector3::new_normalize(Vector3::new(o_x, o_y, o_z));
129 OrientationVector { o_vector, theta }
130 }
131
132 pub fn to_quaternion(&self) -> Quaternion<f64> {
133 let lat = self.o_vector.z.acos();
134 let lon = match self.o_vector.z {
137 val if 1.0 - val.abs() > ANGLE_ACCEPTANCE => self.o_vector.y.atan2(self.o_vector.x),
138 _ => 0.0,
139 };
140
141 let s: [f64; 3] = [
144 (lon / 2.0).sin(),
145 (lat / 2.0).sin(),
146 (self.theta / 2.0).sin(),
147 ];
148
149 let c: [f64; 3] = [
150 (lon / 2.0).cos(),
151 (lat / 2.0).cos(),
152 (self.theta / 2.0).cos(),
153 ];
154
155 let real = c[0] * c[1] * c[2] - s[0] * c[1] * s[2];
156 let i = c[0] * s[1] * s[2] - s[0] * s[1] * c[2];
157 let j = c[0] * s[1] * c[2] + s[0] * s[1] * s[2];
158 let k = s[0] * c[1] * c[2] + c[0] * c[1] * s[2];
159
160 Quaternion::new(real, i, j, k)
161 }
162}
163
164impl ApproxEq for OrientationVector {
165 type Margin = F64Margin;
166
167 fn approx_eq<M: Into<Self::Margin>>(self, other: Self, margin: M) -> bool {
168 let margin = margin.into();
169 let vec_diff = self.o_vector.into_inner() - other.o_vector.into_inner();
170 vec_diff.norm_squared().approx_eq(0.0, margin) && self.theta.approx_eq(other.theta, margin)
171 }
172}
173
174impl From<Quaternion<f64>> for OrientationVector {
175 fn from(quat: Quaternion<f64>) -> Self {
176 let x_quat = Quaternion::new(0.0, -1.0, 0.0, 0.0);
177 let z_quat = Quaternion::new(0.0, 0.0, 0.0, 1.0);
178
179 let conj = quat.conjugate();
180 let new_x = (quat * x_quat) * conj;
181 let new_z = (quat * z_quat) * conj;
182
183 let o_vector = UnitVector3::new_normalize(new_z.imag());
184 let theta = match 1.0 - new_z.k.abs() > ANGLE_ACCEPTANCE {
186 true => {
187 let new_z_imag = new_z.imag();
188 let new_x_imag = new_x.imag();
189 let z_imag_axis = Vector3::z_axis();
190
191 let normal_1 = new_z_imag.cross(&new_x_imag);
192 let normal_2 = new_z_imag.cross(&z_imag_axis);
193 let cos_theta_cand = normal_1.dot(&normal_2) / (normal_1.norm() * normal_2.norm());
194 let cos_theta = match cos_theta_cand {
195 val if val < -1.0 => -1.0,
196 val if val > 1.0 => 1.0,
197 _ => cos_theta_cand,
198 };
199
200 match cos_theta.acos() {
201 val if val > ANGLE_ACCEPTANCE => {
202 let new_z_imag_unit = UnitVector3::new_normalize(new_z_imag);
203 let rot_quat_unit =
204 UnitQuaternion::from_axis_angle(&new_z_imag_unit, -1.0 * val);
205 let rot_quat = rot_quat_unit.quaternion();
206 let z_axis_quat = Quaternion::new(0.0, 0.0, 0.0, 1.0);
207 let test_z = (rot_quat * z_axis_quat) * rot_quat.conjugate();
208 let test_z_imag = test_z.imag();
209
210 let normal_3 = new_z_imag.cross(&test_z_imag);
211 let cos_test =
212 normal_1.dot(&normal_3) / (normal_3.norm() * normal_1.norm());
213 match cos_test {
214 val2 if (1.0 - val2) < (ANGLE_ACCEPTANCE * ANGLE_ACCEPTANCE) => {
215 -1.0 * val
216 }
217 _ => val,
218 }
219 }
220 _ => 0.0,
221 }
222 }
223 _ => match new_z.k {
224 val if val < 0.0 => -1.0 * new_x.j.atan2(new_x.i),
225 _ => -1.0 * new_x.j.atan2(new_x.i * -1.0),
226 },
227 };
228 Self { o_vector, theta }
229 }
230}
231
232pub fn rotate_vector_by_quaternion(quat: &Quaternion<f64>, vector: &Vector3<f64>) -> Vector3<f64> {
233 let quat_vec = Vector3::new(quat.i, quat.j, quat.k);
234 let quat_real = quat.w;
235 (2.0 * quat_vec.dot(vector) * quat_vec)
236 + ((quat_real * quat_real - quat_vec.norm_squared()) * vector)
237 + (2.0 * quat_real) * quat_vec.cross(vector)
238}
239
240#[cfg(test)]
241mod tests {
242 use float_cmp::assert_approx_eq;
243 use nalgebra::{Quaternion, Vector3};
244
245 use super::{rotate_vector_by_quaternion, EulerAngles, OrientationVector};
246
247 fn get_quaternion_diff_norm(quat1: &Quaternion<f64>, quat2: &Quaternion<f64>) -> f64 {
248 let quat_diff = quat1.coords - quat2.coords;
249 quat_diff.norm_squared()
250 }
251
252 fn get_vector_diff_norm(vec1: &Vector3<f64>, vec2: &Vector3<f64>) -> f64 {
253 let vec_diff = vec2 - vec1;
254 vec_diff.norm_squared()
255 }
256
257 #[test]
258 fn quaternion_to_orientation_vector_works() {
259 let quat = Quaternion::new(0.7071067811865476, 0.7071067811865476, 0.0, 0.0);
260 let expected_ov = OrientationVector::new(0.0, -1.0, 0.0, 1.5707963267948966);
261 let calc_ov: OrientationVector = quat.into();
262 assert_approx_eq!(OrientationVector, calc_ov, expected_ov);
263
264 let quat2 = Quaternion::new(0.7071067811865476, -0.7071067811865476, 0.0, 0.0);
265 let expected_ov2 = OrientationVector::new(0.0, 1.0, 0.0, -1.5707963267948966);
266 let calc_ov2: OrientationVector = quat2.into();
267 assert_approx_eq!(OrientationVector, calc_ov2, expected_ov2);
268
269 let quat3 = Quaternion::new(0.96, 0.0, -0.28, 0.0);
270 let expected_ov3 =
271 OrientationVector::new(-0.5376, 0.0, 0.8432, -1.0 * std::f64::consts::PI);
272 let calc_ov3: OrientationVector = quat3.into();
273 assert_approx_eq!(OrientationVector, calc_ov3, expected_ov3);
274
275 let quat4 = Quaternion::new(0.96, 0.0, 0.0, -0.28);
276 let expected_ov4 = OrientationVector::new(0.0, 0.0, 1.0, -0.5675882184166557);
277 let calc_ov4: OrientationVector = quat4.into();
278 assert_approx_eq!(OrientationVector, calc_ov4, expected_ov4);
279
280 let quat5 = Quaternion::new(0.96, -0.28, 0.0, 0.0);
281 let expected_ov5 = OrientationVector::new(0.0, 0.5376, 0.8432, -1.5707963267948966);
282 let calc_ov5: OrientationVector = quat5.into();
283 assert_approx_eq!(OrientationVector, calc_ov5, expected_ov5);
284
285 let quat6 = Quaternion::new(0.96, 0.28, 0.0, 0.0);
286 let expected_ov6 = OrientationVector::new(0.0, -0.5376, 0.8432, 1.5707963267948966);
287 let calc_ov6: OrientationVector = quat6.into();
288 assert_approx_eq!(OrientationVector, calc_ov6, expected_ov6);
289
290 let quat7 = Quaternion::new(0.5, -0.5, -0.5, -0.5);
291 let expected_ov7 = OrientationVector::new(0.0, 1.0, 0.0, -1.0 * std::f64::consts::PI);
292 let calc_ov7: OrientationVector = quat7.into();
293 assert_approx_eq!(OrientationVector, calc_ov7, expected_ov7);
294
295 let quat8 = Quaternion::new(
296 0.816632212270443,
297 -0.17555966025413142,
298 0.39198397193979817,
299 0.3855375485164001,
300 );
301 let expected_ov8 = OrientationVector::new(
302 0.5048437942940054,
303 0.5889844266763397,
304 0.631054742867507,
305 0.02,
306 );
307 let calc_ov8: OrientationVector = quat8.into();
308 assert_approx_eq!(OrientationVector, calc_ov8, expected_ov8, epsilon = 0.0001);
309 }
310
311 #[test]
312 fn orientation_vector_to_quaternion_works() {
313 let ov = OrientationVector::new(0.0, -1.0, 0.0, 1.5707963267948966);
314 let expected_quat = Quaternion::new(0.7071067811865476, 0.7071067811865476, 0.0, 0.0);
315 let calc_quat = ov.to_quaternion();
316 let mut diff = get_quaternion_diff_norm(&expected_quat, &calc_quat);
317 assert_approx_eq!(f64, diff, 0.0);
318
319 let ov2 = OrientationVector::new(0.0, 1.0, 0.0, -1.5707963267948966);
320 let expected_quat2 = Quaternion::new(0.7071067811865476, -0.7071067811865476, 0.0, 0.0);
321 let calc_quat2 = ov2.to_quaternion();
322 diff = get_quaternion_diff_norm(&expected_quat2, &calc_quat2);
323 assert_approx_eq!(f64, diff, 0.0);
324
325 let ov3 = OrientationVector::new(-0.5376, 0.0, 0.8432, -1.0 * std::f64::consts::PI);
326 let expected_quat3 = Quaternion::new(0.96, 0.0, -0.28, 0.0);
327 let calc_quat3 = ov3.to_quaternion();
328 diff = get_quaternion_diff_norm(&expected_quat3, &calc_quat3);
329 assert_approx_eq!(f64, diff, 0.0);
330
331 let ov4 = OrientationVector::new(0.0, 0.0, 1.0, -0.5675882184166557);
332 let expected_quat4 = Quaternion::new(0.96, 0.0, 0.0, -0.28);
333 let calc_quat4 = ov4.to_quaternion();
334 diff = get_quaternion_diff_norm(&expected_quat4, &calc_quat4);
335 assert_approx_eq!(f64, diff, 0.0);
336
337 let ov5 = OrientationVector::new(0.0, 0.5376, 0.8432, -1.5707963267948966);
338 let expected_quat5 = Quaternion::new(0.96, -0.28, 0.0, 0.0);
339 let calc_quat5 = ov5.to_quaternion();
340 diff = get_quaternion_diff_norm(&expected_quat5, &calc_quat5);
341 assert_approx_eq!(f64, diff, 0.0);
342
343 let ov6 = OrientationVector::new(0.0, -0.5376, 0.8432, 1.5707963267948966);
344 let expected_quat6 = Quaternion::new(0.96, 0.28, 0.0, 0.0);
345 let calc_quat6 = ov6.to_quaternion();
346 diff = get_quaternion_diff_norm(&expected_quat6, &calc_quat6);
347 assert_approx_eq!(f64, diff, 0.0);
348
349 let ov7 = OrientationVector::new(0.0, 1.0, 0.0, -1.0 * std::f64::consts::PI);
350 let expected_quat7 = Quaternion::new(0.5, -0.5, -0.5, -0.5);
351 let calc_quat7 = ov7.to_quaternion();
352 diff = get_quaternion_diff_norm(&expected_quat7, &calc_quat7);
353 assert_approx_eq!(f64, diff, 0.0);
354
355 let ov8 = OrientationVector::new(
356 0.5048437942940054,
357 0.5889844266763397,
358 0.631054742867507,
359 0.02,
360 );
361 let expected_quat8 = Quaternion::new(
362 0.816632212270443,
363 -0.17555966025413142,
364 0.39198397193979817,
365 0.3855375485164001,
366 );
367 let calc_quat8 = ov8.to_quaternion();
368 diff = get_quaternion_diff_norm(&expected_quat8, &calc_quat8);
369 assert_approx_eq!(f64, diff, 0.0);
370 }
371
372 #[test]
375 fn orientation_vector_to_quaternion_pins_longitude_at_south_pole() {
376 let o_z = -0.99999; let r = 0.004472124774634615; let theta = 0.5;
379
380 let expected_quat = Quaternion::new(
381 0.002166554039183957,
382 0.24740334074385167,
383 0.9689099994265626,
384 0.0005532120707944279,
385 );
386
387 let ov_45 =
388 OrientationVector::new(0.0031622697544564974, 0.0031622697544564965, o_z, theta);
389 let diff_45 = get_quaternion_diff_norm(&expected_quat, &ov_45.to_quaternion());
390 assert_approx_eq!(f64, diff_45, 0.0);
391
392 let ov_90 = OrientationVector::new(0.0, r, o_z, theta);
393 let diff_90 = get_quaternion_diff_norm(&expected_quat, &ov_90.to_quaternion());
394 assert_approx_eq!(f64, diff_90, 0.0);
395 }
396
397 #[test]
399 fn orientation_vector_to_quaternion_pins_longitude_at_north_pole() {
400 let o_z = 0.99999;
401 let r = 0.004472124774634615;
402 let theta = 0.5;
403
404 let expected_quat = Quaternion::new(
405 0.9689099994265626,
406 0.0005532120707944427,
407 0.0021665540391840148,
408 0.24740334074385167,
409 );
410
411 let ov_45 =
412 OrientationVector::new(0.0031622697544564974, 0.0031622697544564965, o_z, theta);
413 let diff_45 = get_quaternion_diff_norm(&expected_quat, &ov_45.to_quaternion());
414 assert_approx_eq!(f64, diff_45, 0.0);
415
416 let ov_90 = OrientationVector::new(0.0, r, o_z, theta);
417 let diff_90 = get_quaternion_diff_norm(&expected_quat, &ov_90.to_quaternion());
418 assert_approx_eq!(f64, diff_90, 0.0);
419 }
420
421 #[test]
424 fn quaternion_orientation_vector_round_trip_near_south_pole() {
425 let quat = Quaternion::new(
426 0.0017899298388043034,
427 -0.1422149214452539,
428 0.9898332769301709,
429 0.0013402056454645229,
430 );
431 let ov: OrientationVector = quat.into();
432 let diff = get_quaternion_diff_norm(&quat, &ov.to_quaternion());
433 assert_approx_eq!(f64, diff, 0.0, epsilon = 1e-5);
434
435 let closer_quat = Quaternion::new(
436 1.7899298135059344e-05,
437 -0.14221527694833697,
438 0.9898357512751839,
439 1.34020562652243e-05,
440 );
441 let closer_ov: OrientationVector = closer_quat.into();
442 let closer_diff = get_quaternion_diff_norm(&closer_quat, &closer_ov.to_quaternion());
443 assert_approx_eq!(f64, closer_diff, 0.0, epsilon = 1e-9);
444 }
445
446 #[test]
447 fn euler_angles_from_quaternion_works() {
448 let quat = Quaternion::new(
449 0.2705980500730985,
450 -0.6532814824381882,
451 0.27059805007309856,
452 0.6532814824381883,
453 );
454 let euler_angles: EulerAngles = quat.into();
455 assert_approx_eq!(f64, euler_angles.pitch, std::f64::consts::PI / 2.0);
456 assert_approx_eq!(f64, euler_angles.yaw, std::f64::consts::PI);
457 assert_approx_eq!(f64, euler_angles.roll, std::f64::consts::PI / 4.0);
458
459 let quat2 = Quaternion::new(
460 0.4619397662556435,
461 -0.19134171618254486,
462 0.4619397662556434,
463 0.7325378163287418,
464 );
465 let euler_angles2: EulerAngles = quat2.into();
466 assert_approx_eq!(f64, euler_angles2.pitch, std::f64::consts::PI / 4.0);
467 assert_approx_eq!(f64, euler_angles2.yaw, 3.0 * std::f64::consts::PI / 4.0);
468 assert_approx_eq!(f64, euler_angles2.roll, std::f64::consts::PI / 4.0);
469 }
470
471 #[test]
472 fn rotation_by_quaternion_works() {
473 let quat = Quaternion::new(0.7071068, 0.0, 0.7071068, 0.0);
475 let vector = Vector3::new(0.0, 0.0, 1.0);
476 let expected_vector = Vector3::new(1.0, 0.0, 0.0);
477 let rotated_vector = rotate_vector_by_quaternion(&quat, &vector);
478 let diff = get_vector_diff_norm(&expected_vector, &rotated_vector);
479 assert_approx_eq!(f64, diff, 0.0, epsilon = 0.0001);
480
481 let quat2 = Quaternion::new(0.0436194, 0.3710372, 0.5565558, 0.7420744);
483 let vector2 = Vector3::new(4.5, 1.3, 2.0);
484 let expected_vector2 = Vector3::new(-1.593, 3.247, 3.586);
485 let rotated_vector2 = rotate_vector_by_quaternion(&quat2, &vector2);
486 let diff = get_vector_diff_norm(&expected_vector2, &rotated_vector2);
487 assert_approx_eq!(f64, diff, 0.0, epsilon = 0.0001);
488 }
489}