1use {
2 crate::Quaternion,
3 core::{
4 borrow::Borrow,
5 ops::{Add, Mul, Neg, Sub},
6 },
7 num_traits::{ConstOne, ConstZero, Inv, Num, One, Zero},
8};
9
10#[cfg(feature = "unstable")]
11#[cfg(any(feature = "std", feature = "libm"))]
12use crate::PureQuaternion;
13
14#[cfg(any(feature = "std", feature = "libm"))]
15use {
16 core::num::FpCategory,
17 num_traits::{Float, FloatConst},
18};
19
20#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
76pub struct UnitQuaternion<T>(Quaternion<T>);
77
78pub type UQ32 = UnitQuaternion<f32>;
80pub type UQ64 = UnitQuaternion<f64>;
82
83#[cfg(feature = "unstable")]
84#[cfg(any(feature = "std", feature = "libm"))]
85impl<T> UnitQuaternion<T> {
86 pub(crate) fn new(w: T, x: T, y: T, z: T) -> Self {
87 Self(Quaternion::new(w, x, y, z))
88 }
89}
90
91#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
93pub struct EulerAngles<T> {
94 pub roll: T,
96 pub pitch: T,
98 pub yaw: T,
100}
101
102#[cfg(feature = "serde")]
103impl<T> serde::Serialize for EulerAngles<T>
104where
105 T: serde::Serialize,
106{
107 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108 where
109 S: serde::Serializer,
110 {
111 (&self.roll, &self.pitch, &self.yaw).serialize(serializer)
112 }
113}
114
115#[cfg(feature = "serde")]
116impl<'de, T> serde::Deserialize<'de> for EulerAngles<T>
117where
118 T: serde::Deserialize<'de>,
119{
120 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
121 where
122 D: serde::Deserializer<'de>,
123 {
124 let (roll, pitch, yaw) = serde::Deserialize::deserialize(deserializer)?;
125 Ok(Self { roll, pitch, yaw })
126 }
127}
128
129#[cfg(any(feature = "std", feature = "libm"))]
130impl<T> UnitQuaternion<T>
131where
132 T: Float,
133{
134 pub fn from_euler_angles(roll: T, pitch: T, yaw: T) -> Self {
143 let half = T::one() / (T::one() + T::one());
144 let (sr, cr) = (roll * half).sin_cos();
145 let (sp, cp) = (pitch * half).sin_cos();
146 let (sy, cy) = (yaw * half).sin_cos();
147 Self(Quaternion::new(
148 cr * cp * cy + sr * sp * sy,
149 sr * cp * cy - cr * sp * sy,
150 cr * sp * cy + sr * cp * sy,
151 cr * cp * sy - sr * sp * cy,
152 ))
153 }
154
155 #[cfg(feature = "unstable")]
169 pub fn from_euler_angles_struct(angles: EulerAngles<T>) -> Self {
170 let EulerAngles { roll, pitch, yaw } = angles;
171 Self::from_euler_angles(roll, pitch, yaw)
172 }
173}
174
175#[cfg(any(feature = "std", feature = "libm"))]
176impl<T> UnitQuaternion<T>
177where
178 T: Float + FloatConst,
179{
180 pub fn to_euler_angles(&self) -> EulerAngles<T> {
190 let &Self(Quaternion { w, x, y, z }) = self;
191
192 let one = T::one();
193 let two = one + one;
194 let epsilon = T::epsilon();
195 let half_pi = T::FRAC_PI_2();
196
197 let sin_pitch = two * (w * y - z * x);
199
200 if sin_pitch.abs() >= one - epsilon {
202 if sin_pitch >= one - epsilon {
204 let pitch = half_pi; let roll = T::zero();
206 let yaw = -two * T::atan2(x, w);
207 EulerAngles { roll, pitch, yaw }
208 } else {
209 let pitch = -half_pi; let roll = T::zero();
211 let yaw = two * T::atan2(x, w);
212 EulerAngles { roll, pitch, yaw }
213 }
214 } else {
215 let pitch = sin_pitch.asin();
217 let roll =
218 T::atan2(two * (w * x + y * z), one - two * (x * x + y * y));
219 let yaw =
220 T::atan2(two * (w * z + x * y), one - two * (y * y + z * z));
221 EulerAngles { roll, pitch, yaw }
222 }
223 }
224}
225
226#[cfg(any(feature = "std", feature = "libm"))]
227impl<T> UnitQuaternion<T>
228where
229 T: Float,
230{
231 pub fn from_rotation_vector(v: &[T; 3]) -> Self {
249 let sqr_norm = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
250 const PI_SQUARED_F64: f64 =
251 core::f64::consts::PI * core::f64::consts::PI;
252 let pi_square = T::from(PI_SQUARED_F64).unwrap();
253 if T::epsilon() >= T::from(f32::EPSILON).unwrap()
254 && sqr_norm <= pi_square
255 {
256 Self::from_rotation_vector_f32_polynomial(v, sqr_norm)
257 } else {
258 Self::from_rotation_vector_generic(v, sqr_norm)
259 }
260 }
261
262 #[inline]
293 pub fn from_rotation_vector_f32_polynomial(
294 v: &[T; 3],
295 sqr_norm: T,
296 ) -> Self {
297 let x = sqr_norm.to_f32().unwrap();
298 let half_sinc_half_norm = (((2.6051662e-6f32 / 512.0f32 * x
301 - 0.00019809046f32 / 128.0f32)
302 * x
303 + 0.008333051f32 / 32.0f32)
304 * x
305 - 0.16666658f32 / 8.0f32)
306 * x
307 + 1.0f32 / 2.0f32;
308 let cos_half_norm = (((2.3153174e-5f32 / 256.0f32 * x
309 - 0.0013853667f32 / 64.0f32)
310 * x
311 + 0.04166358f32 / 16.0f32)
312 * x
313 - 0.49999905f32 / 4.0f32)
314 * x
315 + 0.99999994f32;
316 let sinc_half_norm = T::from(half_sinc_half_norm).unwrap();
317 let cos_half_norm = T::from(cos_half_norm).unwrap();
318 Self(Quaternion::new(
319 cos_half_norm,
320 v[0] * sinc_half_norm,
321 v[1] * sinc_half_norm,
322 v[2] * sinc_half_norm,
323 ))
324 }
325
326 #[inline]
333 pub fn from_rotation_vector_generic(v: &[T; 3], sqr_norm: T) -> Self {
334 let two = T::one() + T::one();
335 match sqr_norm.classify() {
336 FpCategory::Normal => {
337 let norm = sqr_norm.sqrt();
338 let (sine, cosine) = (norm / two).sin_cos();
339 let f = sine / norm;
340 Self(Quaternion::new(cosine, v[0] * f, v[1] * f, v[2] * f))
341 }
342 FpCategory::Zero | FpCategory::Subnormal => Self(Quaternion::new(
343 T::one(),
346 v[0] / two,
347 v[1] / two,
348 v[2] / two,
349 )),
350 FpCategory::Nan | FpCategory::Infinite => Self(Quaternion::nan()),
351 }
352 }
353}
354
355#[cfg(any(feature = "std", feature = "libm"))]
356impl<T> UnitQuaternion<T>
357where
358 T: Float + FloatConst,
359{
360 pub fn to_rotation_vector(&self) -> [T; 3] {
374 let q = self.as_quaternion();
375 let one = T::one();
376 let two = one + one;
377 let epsilon = T::epsilon();
378
379 let small_abs_real_part = q.w.abs() < T::from(0.9).unwrap();
384
385 let sin_half_angle = if small_abs_real_part {
387 (one - q.w * q.w).sqrt()
388 } else {
389 (q.x * q.x + q.y * q.y + q.z * q.z).sqrt()
392 };
393
394 let angle = two
396 * if small_abs_real_part {
397 q.w.acos()
398 } else if q.w.is_sign_positive() {
399 if sin_half_angle < epsilon.sqrt() {
401 return [two * q.x, two * q.y, two * q.z];
405 }
406 sin_half_angle.asin()
407 } else {
408 if sin_half_angle.is_zero() {
410 return [two * T::PI(), T::zero(), T::zero()];
413 }
414 let pi_minus_half_angle = sin_half_angle.asin();
415 T::PI() - pi_minus_half_angle
416 };
417
418 let x = q.x / sin_half_angle;
420 let y = q.y / sin_half_angle;
421 let z = q.z / sin_half_angle;
422
423 [x * angle, y * angle, z * angle]
424 }
425}
426
427impl<T> UnitQuaternion<T>
428where
429 T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + One + Clone,
430{
431 #[inline]
463 pub fn to_rotation_matrix3x3(self) -> [T; 9] {
464 let two = T::one() + T::one();
465
466 let Self(Quaternion { w, x, y, z }) = self;
467
468 let wx = two.clone() * w.clone() * x.clone();
469 let wy = two.clone() * w.clone() * y.clone();
470 let wz = two.clone() * w * z.clone();
471 let xx = two.clone() * x.clone() * x.clone();
472 let xy = two.clone() * x.clone() * y.clone();
473 let xz = two.clone() * x * z.clone();
474 let yy = two.clone() * y.clone() * y.clone();
475 let yz = two.clone() * y * z.clone();
476 let zz = two * z.clone() * z;
477
478 [
479 T::one() - yy.clone() - zz.clone(),
480 xy.clone() - wz.clone(),
481 xz.clone() + wy.clone(),
482 xy + wz,
483 T::one() - xx.clone() - zz,
484 yz.clone() - wx.clone(),
485 xz - wy,
486 yz + wx,
487 T::one() - xx - yy,
488 ]
489 }
490}
491
492pub trait ReadMat3x3<T> {
494 fn at(&self, row: usize, col: usize) -> &T;
498}
499
500impl<T> ReadMat3x3<T> for [T; 9] {
501 fn at(&self, row: usize, col: usize) -> &T {
502 &self[col + 3 * row]
503 }
504}
505
506impl<T> ReadMat3x3<T> for [[T; 3]; 3] {
507 fn at(&self, row: usize, col: usize) -> &T {
508 &self[row][col]
509 }
510}
511
512#[cfg(any(feature = "std", feature = "libm"))]
519impl<T> UnitQuaternion<T>
520where
521 T: Float,
522{
523 pub fn from_rotation_matrix3x3(
543 mat: &impl ReadMat3x3<T>,
544 ) -> UnitQuaternion<T> {
545 let zero = T::zero();
546 let one = T::one();
547 let two = one + one;
548 let quarter = one / (two * two);
549 let m00 = mat.at(0, 0);
550 let m11 = mat.at(1, 1);
551 let m22 = mat.at(2, 2);
552 let trace: T = *m00 + *m11 + *m22;
553
554 if trace > zero {
559 let s = (trace + one).sqrt() * two; let qw = quarter * s;
561 let qx = (*mat.at(2, 1) - *mat.at(1, 2)) / s;
562 let qy = (*mat.at(0, 2) - *mat.at(2, 0)) / s;
563 let qz = (*mat.at(1, 0) - *mat.at(0, 1)) / s;
564 Self(Quaternion::new(qw, qx, qy, qz))
565 } else if (m00 > m11) && (m00 > m22) {
566 let s = (one + *m00 - *m11 - *m22).sqrt() * two; let qw = (*mat.at(2, 1) - *mat.at(1, 2)) / s;
568 let qx = quarter * s;
569 let qy = (*mat.at(0, 1) + *mat.at(1, 0)) / s;
570 let qz = (*mat.at(0, 2) + *mat.at(2, 0)) / s;
571 if *mat.at(2, 1) >= *mat.at(1, 2) {
572 Self(Quaternion::new(qw, qx, qy, qz))
573 } else {
574 Self(Quaternion::new(-qw, -qx, -qy, -qz))
575 }
576 } else if m11 > m22 {
577 let s = (one + *m11 - *m00 - *m22).sqrt() * two; let qw = (*mat.at(0, 2) - *mat.at(2, 0)) / s;
579 let qx = (*mat.at(0, 1) + *mat.at(1, 0)) / s;
580 let qy = quarter * s;
581 let qz = (*mat.at(1, 2) + *mat.at(2, 1)) / s;
582 if *mat.at(0, 2) >= *mat.at(2, 0) {
583 Self(Quaternion::new(qw, qx, qy, qz))
584 } else {
585 Self(Quaternion::new(-qw, -qx, -qy, -qz))
586 }
587 } else {
588 let s = (one + *m22 - *m00 - *m11).sqrt() * two; let qw = (*mat.at(1, 0) - *mat.at(0, 1)) / s;
590 let qx = (*mat.at(0, 2) + *mat.at(2, 0)) / s;
591 let qy = (*mat.at(1, 2) + *mat.at(2, 1)) / s;
592 let qz = quarter * s;
593 if *mat.at(1, 0) >= *mat.at(0, 1) {
594 Self(Quaternion::new(qw, qx, qy, qz))
595 } else {
596 Self(Quaternion::new(-qw, -qx, -qy, -qz))
597 }
598 }
599 }
600}
601
602#[cfg(any(feature = "std", feature = "libm"))]
603impl<T> UnitQuaternion<T>
604where
605 T: Float,
606{
607 fn make_perpendicular_unit_vector(a: &[T; 3]) -> [T; 3] {
610 let zero = T::zero();
613 let a_sqr = [a[0] * a[0], a[1] * a[1], a[2] * a[2]];
614 if a_sqr[0] <= a_sqr[1] {
615 if a_sqr[0] <= a_sqr[2] {
616 let norm = (a_sqr[1] + a_sqr[2]).sqrt();
618 [zero, a[2] / norm, -a[1] / norm]
619 } else {
620 let norm = (a_sqr[0] + a_sqr[1]).sqrt();
622 [a[1] / norm, -a[0] / norm, zero]
623 }
624 } else if a_sqr[1] <= a_sqr[2] {
625 let norm = (a_sqr[0] + a_sqr[2]).sqrt();
627 [a[2] / norm, zero, -a[0] / norm]
628 } else {
629 let norm = (a_sqr[0] + a_sqr[1]).sqrt();
631 [a[1] / norm, -a[0] / norm, zero]
632 }
633 }
634
635 pub fn from_two_vectors(a: &[T; 3], b: &[T; 3]) -> UnitQuaternion<T> {
661 let zero = T::zero();
693 let one = T::one();
694 let two = one + one;
695 let half = one / two;
696
697 let a_norm = a[0].hypot(a[1].hypot(a[2]));
699 let b_norm = b[0].hypot(b[1].hypot(b[2]));
700 if a_norm.is_zero() || b_norm.is_zero() {
701 return UnitQuaternion::one();
702 }
703 let a = [a[0] / a_norm, a[1] / a_norm, a[2] / a_norm];
704 let b = [b[0] / b_norm, b[1] / b_norm, b[2] / b_norm];
705
706 let v = [
708 a[1] * b[2] - a[2] * b[1],
709 a[2] * b[0] - a[0] * b[2],
710 a[0] * b[1] - a[1] * b[0],
711 ];
712
713 let d = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
715
716 let wx2 = if d >= -half {
717 (two * d + two).sqrt()
719 } else {
720 let v_norm_sqr = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
722 if v_norm_sqr.is_zero() {
723 let [x, y, z] = Self::make_perpendicular_unit_vector(&a);
725 return UnitQuaternion(Quaternion::new(zero, x, y, z));
726 }
727 (v_norm_sqr / (half - d * half)).sqrt()
729 };
730
731 UnitQuaternion(Quaternion::new(
733 wx2 / two,
734 v[0] / wx2,
735 v[1] / wx2,
736 v[2] / wx2,
737 ))
738 }
739}
740
741#[cfg(any(feature = "std", feature = "libm"))]
742impl<T> UnitQuaternion<T>
743where
744 T: Float,
745{
746 #[inline]
770 pub fn normalize(q: Quaternion<T>) -> Option<Self> {
771 let norm = q.norm();
772 match norm.classify() {
773 FpCategory::Normal | FpCategory::Subnormal => {
774 Some(UnitQuaternion(q / norm))
775 }
776 _ => None,
777 }
778 }
779}
780
781impl<T> Default for UnitQuaternion<T>
782where
783 T: Num + Clone,
784{
785 #[inline]
786 fn default() -> Self {
787 Self::one()
788 }
789}
790
791impl<T> UnitQuaternion<T>
792where
793 T: ConstZero + ConstOne,
794{
795 pub const ONE: Self = Self(Quaternion::ONE);
806
807 pub const I: Self = Self(Quaternion::I);
818
819 pub const J: Self = Self(Quaternion::J);
830
831 pub const K: Self = Self(Quaternion::K);
842}
843
844impl<T> ConstOne for UnitQuaternion<T>
845where
846 T: ConstZero + ConstOne + Num + Clone,
847{
848 const ONE: Self = Self::ONE;
849}
850
851impl<T> One for UnitQuaternion<T>
852where
853 T: Num + Clone,
854{
855 #[inline]
856 fn one() -> Self {
857 Self(Quaternion::one())
858 }
859
860 #[inline]
861 fn is_one(&self) -> bool {
862 self.0.is_one()
863 }
864
865 #[inline]
866 fn set_one(&mut self) {
867 self.0.set_one();
868 }
869}
870
871impl<T> UnitQuaternion<T>
872where
873 T: Zero + One,
874{
875 #[inline]
886 pub fn one() -> Self {
887 Self(Quaternion::new(T::one(), T::zero(), T::zero(), T::zero()))
888 }
889
890 #[inline]
901 pub fn i() -> Self {
902 Self(Quaternion::i())
903 }
904
905 #[inline]
916 pub fn j() -> Self {
917 Self(Quaternion::j())
918 }
919
920 #[inline]
931 pub fn k() -> Self {
932 Self(Quaternion::k())
933 }
934}
935
936impl<T> UnitQuaternion<T> {
937 #[inline]
952 pub fn into_quaternion(self) -> Quaternion<T> {
953 self.0
954 }
955
956 #[inline]
972 pub fn into_inner(self) -> Quaternion<T> {
973 self.into_quaternion()
974 }
975
976 #[inline]
987 pub fn as_quaternion(&self) -> &Quaternion<T> {
988 &self.0
989 }
990}
991
992impl<T> Borrow<Quaternion<T>> for UnitQuaternion<T> {
993 fn borrow(&self) -> &Quaternion<T> {
994 self.as_quaternion()
995 }
996}
997
998impl<T> UnitQuaternion<T>
999where
1000 T: Clone + Neg<Output = T>,
1001{
1002 #[inline]
1013 pub fn conj(&self) -> Self {
1014 Self(self.0.conj())
1015 }
1016}
1017
1018impl<T> UnitQuaternion<T>
1019where
1020 T: Clone + Neg<Output = T>,
1021{
1022 #[inline]
1035 pub fn inv(&self) -> Self {
1036 self.conj()
1037 }
1038}
1039
1040impl<T> Inv for &UnitQuaternion<T>
1041where
1042 T: Clone + Neg<Output = T>,
1043{
1044 type Output = UnitQuaternion<T>;
1045
1046 #[inline]
1047 fn inv(self) -> Self::Output {
1048 self.conj()
1049 }
1050}
1051
1052impl<T> Inv for UnitQuaternion<T>
1053where
1054 T: Clone + Neg<Output = T>,
1055{
1056 type Output = UnitQuaternion<T>;
1057
1058 #[inline]
1059 fn inv(self) -> Self::Output {
1060 self.conj()
1061 }
1062}
1063
1064impl<T> Mul<UnitQuaternion<T>> for UnitQuaternion<T>
1065where
1066 Quaternion<T>: Mul<Output = Quaternion<T>>,
1067{
1068 type Output = UnitQuaternion<T>;
1069
1070 #[inline]
1071 fn mul(self, rhs: UnitQuaternion<T>) -> Self::Output {
1072 Self(self.into_inner() * rhs.into_inner())
1073 }
1074}
1075
1076impl<T> Neg for UnitQuaternion<T>
1077where
1078 T: Neg<Output = T>,
1079{
1080 type Output = UnitQuaternion<T>;
1081
1082 fn neg(self) -> Self::Output {
1083 Self(-self.0)
1084 }
1085}
1086
1087impl<T> UnitQuaternion<T>
1088where
1089 T: Add<T, Output = T> + Mul<T, Output = T>,
1090{
1091 #[inline]
1104 pub fn dot(self, other: Self) -> T {
1105 self.0.dot(other.0)
1106 }
1107}
1108
1109#[cfg(any(feature = "std", feature = "libm"))]
1110impl<T> UnitQuaternion<T>
1111where
1112 T: Float,
1113{
1114 #[inline]
1134 pub fn adjust_norm(self) -> Self {
1135 self.0
1137 .normalize()
1138 .expect("Unit quaternion value too inaccurate. Cannot renormalize.")
1139 }
1140}
1141
1142impl<T> UnitQuaternion<T>
1143where
1144 T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Clone,
1145{
1146 pub fn rotate_vector(self, vector: [T; 3]) -> [T; 3] {
1162 let q = self.into_quaternion();
1163 let [vx, vy, vz] = vector;
1164 let v_q_inv = Quaternion::<T>::new(
1165 vx.clone() * q.x.clone()
1166 + vy.clone() * q.y.clone()
1167 + vz.clone() * q.z.clone(),
1168 vx.clone() * q.w.clone() - vy.clone() * q.z.clone()
1169 + vz.clone() * q.y.clone(),
1170 vx.clone() * q.z.clone() + vy.clone() * q.w.clone()
1171 - vz.clone() * q.x.clone(),
1172 vy * q.x.clone() - vx * q.y.clone() + vz * q.w.clone(),
1173 );
1174 let result = q * v_q_inv;
1175 [result.x, result.y, result.z]
1176 }
1177}
1178
1179#[cfg(any(feature = "std", feature = "libm"))]
1180impl<T> UnitQuaternion<T>
1181where
1182 T: Float,
1183{
1184 pub fn slerp(&self, other: &Self, t: T) -> Self {
1198 let one = T::one();
1199 let dot = self.dot(*other);
1200
1201 let (dot, other) = if dot.is_sign_positive() {
1204 (dot, *other)
1205 } else {
1206 (-dot, -*other)
1207 };
1208
1209 let threshold = one - T::epsilon().sqrt();
1212 if dot > threshold {
1213 return Self(*self + (other - *self) * t);
1215 }
1216
1217 let theta_0 = dot.acos();
1219 let theta = theta_0 * t;
1221
1222 let sin_theta = theta.sin();
1224 let sin_theta_0 = theta_0.sin();
1225 let s0 = ((one - t) * theta_0).sin() / sin_theta_0;
1226 let s1 = sin_theta / sin_theta_0;
1227
1228 Self(*self * s0 + other * s1)
1231 }
1232}
1233
1234#[cfg(any(feature = "std", feature = "libm"))]
1235impl<T> UnitQuaternion<T>
1236where
1237 T: Float + FloatConst,
1238{
1239 pub fn sqrt(self) -> Self {
1261 let zero = T::zero();
1262 let one = T::one();
1263 let two = one + one;
1264 let half = one / two;
1265 let UnitQuaternion(c) = self;
1266
1267 if c.w >= -half {
1268 let wx2 = (c.w * two + two).sqrt();
1283
1284 UnitQuaternion(Quaternion::new(
1285 wx2 * half,
1286 c.x / wx2,
1287 c.y / wx2,
1288 c.z / wx2,
1289 ))
1290 } else {
1291 let im_norm_sqr = c.y * c.y + (c.x * c.x + c.z * c.z);
1299 if im_norm_sqr >= T::min_positive_value() {
1300 let wx2 = (im_norm_sqr * two / (one - c.w)).sqrt();
1302 UnitQuaternion(Quaternion::new(
1303 wx2 * half,
1304 c.x / wx2,
1305 c.y / wx2,
1306 c.z / wx2,
1307 ))
1308 } else if c.x.is_zero() && c.y.is_zero() && c.z.is_zero() {
1309 UnitQuaternion(Quaternion::new(
1312 zero,
1313 one.copysign(c.x),
1314 c.y,
1315 c.z,
1316 ))
1317 } else {
1318 let s = one / T::min_positive_value();
1320 let sx = s * c.x;
1321 let sy = s * c.y;
1322 let sz = s * c.z;
1323 let im_norm = (sy * sy + (sx * sx + sz * sz)).sqrt() / s;
1324 UnitQuaternion(Quaternion::new(
1325 im_norm * half,
1326 c.x / im_norm,
1327 c.y / im_norm,
1328 c.z / im_norm,
1329 ))
1330 }
1331 }
1332 }
1333}
1334
1335#[cfg(feature = "unstable")]
1336#[cfg(any(feature = "std", feature = "libm"))]
1337impl<T> UnitQuaternion<T>
1338where
1339 T: Float + FloatConst,
1340{
1341 pub fn ln(self) -> PureQuaternion<T> {
1363 let sqr_norm_im =
1365 self.0.x * self.0.x + self.0.y * self.0.y + self.0.z * self.0.z;
1366
1367 if sqr_norm_im <= T::epsilon() {
1368 if self.0.w.is_sign_positive() {
1370 PureQuaternion::new(self.0.x, self.0.y, self.0.z)
1373 } else if self.0.x.is_zero()
1374 && self.0.y.is_zero()
1375 && self.0.z.is_zero()
1376 {
1377 PureQuaternion::new(
1379 T::PI().copysign(self.0.x),
1380 self.0.y,
1381 self.0.z,
1382 )
1383 } else if sqr_norm_im.is_normal() {
1384 let norm_im = sqr_norm_im.sqrt();
1386
1387 let f = T::PI() / norm_im + self.0.w.recip();
1399
1400 PureQuaternion::new(self.0.x, self.0.y, self.0.z) * f
1401 } else {
1402 let f = T::min_positive_value().sqrt() * T::epsilon();
1408 let xf = self.0.x / f;
1409 let yf = self.0.y / f;
1410 let zf = self.0.z / f;
1411 let sqr_sum = xf * xf + yf * yf + zf * zf;
1412 let im_norm_div_f = sqr_sum.sqrt();
1413 let pi_div_f = T::PI() / f;
1414 PureQuaternion::new(
1424 self.0.x * pi_div_f / im_norm_div_f,
1425 self.0.y * pi_div_f / im_norm_div_f,
1426 self.0.z * pi_div_f / im_norm_div_f,
1427 )
1428 }
1429 } else {
1430 let norm_im = if sqr_norm_im.is_normal() {
1434 sqr_norm_im.sqrt()
1436 } else {
1437 let f = T::min_positive_value().sqrt() * T::epsilon();
1441 let xf = self.0.x / f;
1442 let yf = self.0.y / f;
1443 let zf = self.0.z / f;
1444 let sqr_sum = xf * xf + yf * yf + zf * zf;
1445 sqr_sum.sqrt() * f
1446 };
1447 let angle = norm_im.atan2(self.0.w);
1448 let x = self.0.x * angle / norm_im;
1449 let y = self.0.y * angle / norm_im;
1450 let z = self.0.z * angle / norm_im;
1451 PureQuaternion::new(x, y, z)
1452 }
1453 }
1454}
1455
1456#[cfg(feature = "serde")]
1457impl<T> serde::Serialize for UnitQuaternion<T>
1458where
1459 T: serde::Serialize,
1460{
1461 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1462 where
1463 S: serde::Serializer,
1464 {
1465 self.0.serialize(serializer)
1466 }
1467}
1468
1469#[cfg(feature = "serde")]
1470impl<'de, T> serde::Deserialize<'de> for UnitQuaternion<T>
1471where
1472 T: serde::Deserialize<'de>,
1473{
1474 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1475 where
1476 D: serde::Deserializer<'de>,
1477 {
1478 let q = serde::Deserialize::deserialize(deserializer)?;
1479 Ok(Self(q))
1480 }
1481}
1482
1483#[cfg(all(feature = "rand", any(feature = "std", feature = "libm")))]
1484impl<T> rand::distr::Distribution<UnitQuaternion<T>>
1485 for rand::distr::StandardUniform
1486where
1487 T: Float,
1488 rand_distr::StandardNormal: rand_distr::Distribution<T>,
1489{
1490 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> UnitQuaternion<T> {
1491 loop {
1492 let s = rand_distr::StandardNormal;
1493 let w = s.sample(rng);
1494 let x = s.sample(rng);
1495 let y = s.sample(rng);
1496 let z = s.sample(rng);
1497 let q = Quaternion::new(w, x, y, z);
1498 if let Some(q) = q.normalize() {
1499 return q;
1500 }
1501 }
1502 }
1503}
1504
1505#[cfg(test)]
1506mod tests {
1507 use {
1508 crate::{Quaternion, UnitQuaternion, Q32, UQ32, UQ64},
1509 core::borrow::Borrow,
1510 num_traits::{ConstOne, One},
1511 };
1512
1513 #[cfg(feature = "std")]
1514 use {
1515 core::hash::{Hash, Hasher},
1516 std::collections::hash_map::DefaultHasher,
1517 };
1518
1519 #[cfg(any(feature = "std", feature = "libm"))]
1520 use {crate::Q64, num_traits::Inv};
1521
1522 #[cfg(any(feature = "std", feature = "libm", feature = "serde"))]
1523 use crate::EulerAngles;
1524
1525 #[cfg(any(feature = "std", feature = "libm"))]
1526 #[cfg(feature = "unstable")]
1527 use crate::PureQuaternion;
1528
1529 #[cfg(feature = "std")]
1531 fn compute_hash(val: impl Hash) -> u64 {
1532 let mut hasher = DefaultHasher::new();
1533 val.hash(&mut hasher);
1534 hasher.finish()
1535 }
1536
1537 #[cfg(feature = "std")]
1538 #[test]
1539 fn test_hash_of_unit_quaternion_equals_hash_of_inner_quaternion() {
1540 assert_eq!(
1544 compute_hash(UnitQuaternion::<u32>::ONE),
1545 compute_hash(Quaternion::<u32>::ONE)
1546 );
1547 assert_eq!(
1548 compute_hash(UnitQuaternion::<i32>::I),
1549 compute_hash(Quaternion::<i32>::I)
1550 );
1551 assert_eq!(
1552 compute_hash(UnitQuaternion::<isize>::J),
1553 compute_hash(Quaternion::<isize>::J)
1554 );
1555 assert_eq!(
1556 compute_hash(UnitQuaternion::<i128>::K),
1557 compute_hash(Quaternion::<i128>::K)
1558 );
1559 }
1560
1561 #[cfg(feature = "serde")]
1562 #[test]
1563 fn test_serde_euler_angles() {
1564 let angles = EulerAngles {
1566 roll: 1.0,
1567 pitch: 2.0,
1568 yaw: 3.0,
1569 };
1570
1571 let serialized =
1573 serde_json::to_string(&angles).expect("Failed to serialize angles");
1574
1575 let deserialized: EulerAngles<f64> = serde_json::from_str(&serialized)
1577 .expect("Failed to deserialize angles");
1578
1579 assert_eq!(angles, deserialized);
1581 }
1582
1583 #[cfg(any(feature = "std", feature = "libm"))]
1584 #[test]
1585 fn test_from_euler_angles() {
1586 assert!(
1588 (UQ32::from_euler_angles(core::f32::consts::PI, 0.0, 0.0)
1589 .into_quaternion()
1590 - Q32::I)
1591 .norm()
1592 < f32::EPSILON
1593 );
1594 assert!(
1595 (UQ64::from_euler_angles(0.0, core::f64::consts::PI, 0.0)
1596 .into_quaternion()
1597 - Q64::J)
1598 .norm()
1599 < f64::EPSILON
1600 );
1601 assert!(
1602 (UQ32::from_euler_angles(0.0, 0.0, core::f32::consts::PI)
1603 .into_quaternion()
1604 - Q32::K)
1605 .norm()
1606 < f32::EPSILON
1607 );
1608 assert!(
1609 (UQ64::from_euler_angles(1.0, 2.0, 3.0).into_quaternion()
1610 - (UQ64::from_euler_angles(0.0, 0.0, 3.0)
1611 * UQ64::from_euler_angles(0.0, 2.0, 0.0)
1612 * UQ64::from_euler_angles(1.0, 0.0, 0.0))
1613 .into_quaternion())
1614 .norm()
1615 < 4.0 * f64::EPSILON
1616 );
1617 }
1618
1619 #[cfg(all(
1620 feature = "unstable",
1621 feature = "rand",
1622 any(feature = "std", feature = "libm")
1623 ))]
1624 #[test]
1625 fn test_from_euler_angles_struct() {
1626 let angles = EulerAngles {
1629 roll: 1.0,
1630 pitch: 2.0,
1631 yaw: 3.0,
1632 };
1633 let q = UQ64::from_euler_angles_struct(angles);
1634 let expected = UQ64::from_euler_angles(1.0, 2.0, 3.0);
1635 assert_eq!(q, expected);
1636 }
1637
1638 #[cfg(any(feature = "std", feature = "libm"))]
1639 #[test]
1640 fn test_to_euler_angles() {
1641 let test_data = [
1643 Q64::new(1.0, 0.0, 0.0, 0.0), Q64::new(0.0, 1.0, 0.0, 0.0), Q64::new(0.0, 0.0, 1.0, 0.0), Q64::new(0.0, 0.0, 0.0, 1.0), Q64::new(1.0, 1.0, 1.0, 1.0), Q64::new(1.0, -2.0, 3.0, -4.0), Q64::new(4.0, 3.0, 2.0, 1.0), Q64::new(1.0, 0.0, 1.0, 0.0), Q64::new(1.0, 1.0, 1.0, -1.0), Q64::new(1.0, 0.0, -1.0, 0.0), Q64::new(1.0, 1.0, -1.0, 1.0), ];
1655 for q in test_data.into_iter().map(|q| q.normalize().unwrap()) {
1656 let EulerAngles { roll, pitch, yaw } = q.to_euler_angles();
1657 let p = UQ64::from_euler_angles(roll, pitch, yaw);
1658 assert!((p - q).norm() < f64::EPSILON);
1659 }
1660 }
1661
1662 #[cfg(any(feature = "std", feature = "libm"))]
1663 #[test]
1664 fn test_from_rotation_vector() {
1665 assert!(
1667 (UQ32::from_rotation_vector(&[core::f32::consts::PI, 0.0, 0.0])
1668 - Q32::I)
1669 .norm()
1670 < f32::EPSILON
1671 );
1672 assert!(
1673 (UQ64::from_rotation_vector(&[0.0, core::f64::consts::PI, 0.0])
1674 - Q64::J)
1675 .norm()
1676 < f64::EPSILON
1677 );
1678 assert!(
1679 (UQ32::from_rotation_vector(&[0.0, 0.0, core::f32::consts::PI])
1680 - Q32::K)
1681 .norm()
1682 < f32::EPSILON
1683 );
1684 let x = 2.0 * core::f64::consts::FRAC_PI_3 * (1.0f64 / 3.0).sqrt();
1685 assert!(
1686 (UQ64::from_rotation_vector(&[x, x, x])
1687 - Q64::new(0.5, 0.5, 0.5, 0.5))
1688 .norm()
1689 < 4.0 * f64::EPSILON
1690 );
1691 assert!(
1692 (UQ64::from_rotation_vector(&[-x, x, -x])
1693 - Q64::new(0.5, -0.5, 0.5, -0.5))
1694 .norm()
1695 < 4.0 * f64::EPSILON
1696 );
1697 }
1698
1699 #[cfg(any(feature = "std", feature = "libm"))]
1700 #[test]
1701 fn test_from_rotation_vector_infinite() {
1702 let inf = f32::INFINITY;
1704 assert!(UQ32::from_rotation_vector(&[inf, 0.0, 0.0])
1705 .into_inner()
1706 .is_all_nan());
1707 assert!(UQ32::from_rotation_vector(&[inf, inf, inf])
1708 .into_inner()
1709 .is_all_nan());
1710 }
1711
1712 #[cfg(any(feature = "std", feature = "libm"))]
1713 #[test]
1714 fn test_from_rotation_vector_nan_input() {
1715 let nan = f64::NAN;
1717 assert!(UQ64::from_rotation_vector(&[nan, 0.0, 0.0])
1718 .into_inner()
1719 .is_all_nan());
1720 assert!(UQ64::from_rotation_vector(&[nan, nan, nan])
1721 .into_inner()
1722 .is_all_nan());
1723 }
1724
1725 #[cfg(any(feature = "std", feature = "libm"))]
1726 #[test]
1727 fn test_to_rotation_vector_zero_rotation() {
1728 assert_eq!(UQ32::ONE.to_rotation_vector(), [0.0, 0.0, 0.0]);
1730 assert_eq!(UQ64::ONE.to_rotation_vector(), [0.0, 0.0, 0.0]);
1731 }
1732
1733 #[cfg(any(feature = "std", feature = "libm"))]
1734 #[test]
1735 fn test_to_rotation_vector_90_degree_rotation_x_axis() {
1736 let q = Q32::new(1.0, 1.0, 0.0, 0.0).normalize().unwrap();
1738 let rotation_vector = q.to_rotation_vector();
1739 assert!(
1740 (rotation_vector[0] - core::f32::consts::FRAC_PI_2).abs()
1741 < f32::EPSILON
1742 );
1743 assert!((rotation_vector[1]).abs() < f32::EPSILON);
1744 assert!((rotation_vector[2]).abs() < f32::EPSILON);
1745 }
1746
1747 #[cfg(any(feature = "std", feature = "libm"))]
1748 #[test]
1749 fn test_to_rotation_vector_180_degree_rotation_y_axis() {
1750 let q = UQ64::J;
1752 let rotation_vector = q.to_rotation_vector();
1753 assert!((rotation_vector[0]).abs() < f64::EPSILON);
1754 assert!(
1755 (rotation_vector[1] - core::f64::consts::PI).abs() < f64::EPSILON
1756 );
1757 assert!((rotation_vector[2]).abs() < f64::EPSILON);
1758 }
1759
1760 #[cfg(any(feature = "std", feature = "libm"))]
1761 #[test]
1762 fn test_to_rotation_vector_180_degree_rotation_arbitrary_axis() {
1763 let q = Q32::new(0.0, 1.0, 1.0, 1.0).normalize().unwrap();
1765 let rotation_vector = q.to_rotation_vector();
1766 let expected = [
1767 core::f32::consts::PI / (1.0f32 + 1.0 + 1.0).sqrt(),
1768 core::f32::consts::PI / (1.0f32 + 1.0 + 1.0).sqrt(),
1769 core::f32::consts::PI / (1.0f32 + 1.0 + 1.0).sqrt(),
1770 ];
1771 assert!((rotation_vector[0] - expected[0]).abs() < 4.0 * f32::EPSILON);
1772 assert!((rotation_vector[1] - expected[1]).abs() < 4.0 * f32::EPSILON);
1773 assert!((rotation_vector[2] - expected[2]).abs() < 4.0 * f32::EPSILON);
1774 }
1775
1776 #[cfg(any(feature = "std", feature = "libm"))]
1777 #[test]
1778 fn test_to_rotation_vector_small_rotation() {
1779 let angle = 1e-6f32;
1781 let q = Q32::new((angle / 2.0).cos(), (angle / 2.0).sin(), 0.0, 0.0)
1782 .normalize()
1783 .unwrap();
1784 let rotation_vector = q.to_rotation_vector();
1785 assert!((rotation_vector[0] - angle).abs() < f32::EPSILON);
1786 assert!((rotation_vector[1]).abs() < f32::EPSILON);
1787 assert!((rotation_vector[2]).abs() < f32::EPSILON);
1788 }
1789
1790 #[cfg(any(feature = "std", feature = "libm"))]
1791 #[test]
1792 fn test_to_rotation_vector_general_case() {
1793 let min_pos = f64::MIN_POSITIVE;
1798 for q in [
1799 [1.0, 0.0, 0.0, 0.0],
1800 [0.0, 0.0, 0.0, 1.0],
1801 [1.0, 1.0, 0.0, 0.0],
1802 [1.0, 1.0, 1.0, 0.0],
1803 [1.0, 1.0, 1.0, 1.0],
1804 [0.0, 1.0, 1.0, -1.0],
1805 [1.0, 0.0, 2.0, 5.0],
1806 [1.0, 0.0, 1.0e-10, 2.0e-10],
1807 [-1.0, 0.0, 0.0, 0.0],
1808 [1.0, f64::EPSILON, 0.0, 0.0],
1809 [1.0, 0.0, 0.0, min_pos],
1810 [-1.0, 3.0 * min_pos, 2.0 * min_pos, min_pos],
1811 [1.0, 0.1, 0.0, 0.0],
1812 [-1.0, 0.0, 0.1, 0.0],
1813 ]
1814 .into_iter()
1815 .map(|[w, x, y, z]| Q64::new(w, x, y, z).normalize().unwrap())
1816 {
1817 let rot = q.to_rotation_vector();
1818 let p = UQ64::from_rotation_vector(&rot);
1819 assert!((p - q).norm() <= 6.0 * f64::EPSILON);
1820 }
1821 }
1822
1823 #[test]
1824 fn test_rotation_matrix_identity() {
1825 let q = UQ64::ONE;
1827 let rot_matrix = q.to_rotation_matrix3x3();
1828 let expected = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
1829 assert_eq!(rot_matrix, expected);
1830 }
1831
1832 #[cfg(any(feature = "std", feature = "libm"))]
1833 #[test]
1834 fn test_rotation_matrix_90_degrees_x() {
1835 let q = Q64::new(1.0, 1.0, 0.0, 0.0).normalize().unwrap();
1837 let rot_matrix = q.to_rotation_matrix3x3();
1838 let expected = [
1839 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0,
1842 ];
1843 for (r, e) in rot_matrix.iter().zip(expected) {
1844 assert!((r - e).abs() <= f64::EPSILON);
1845 }
1846 }
1847
1848 #[cfg(any(feature = "std", feature = "libm"))]
1849 #[test]
1850 fn test_rotation_matrix_90_degrees_y() {
1851 let q = Q64::new(1.0, 0.0, 1.0, 0.0).normalize().unwrap();
1853 let rot_matrix = q.to_rotation_matrix3x3();
1854 let expected = [
1855 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, -1.0, 0.0, 0.0,
1858 ];
1859 for (r, e) in rot_matrix.iter().zip(expected) {
1860 assert!((r - e).abs() <= f64::EPSILON);
1861 }
1862 }
1863
1864 #[cfg(any(feature = "std", feature = "libm"))]
1865 #[test]
1866 fn test_rotation_matrix_90_degrees_z() {
1867 let q = Q64::new(1.0, 0.0, 0.0, 1.0).normalize().unwrap();
1869 let rot_matrix = q.to_rotation_matrix3x3();
1870 let expected = [
1871 0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
1874 ];
1875 for (r, e) in rot_matrix.iter().zip(expected) {
1876 assert!((r - e).abs() <= f64::EPSILON);
1877 }
1878 }
1879
1880 #[cfg(any(feature = "std", feature = "libm"))]
1881 #[test]
1882 fn test_rotation_matrix_120_degrees_xyz() {
1883 let q = Q64::new(1.0, 1.0, 1.0, 1.0).normalize().unwrap();
1887 let rot_matrix = q.to_rotation_matrix3x3();
1888 let expected = [
1889 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0,
1892 ];
1893 for (r, e) in rot_matrix.iter().zip(expected) {
1894 assert!((r - e).abs() <= f64::EPSILON);
1895 }
1896 }
1897
1898 #[cfg(any(feature = "std", feature = "libm"))]
1899 #[test]
1900 fn test_rotation_matrix_general() {
1901 let q = Q64::new(-1.0, 2.0, -3.0, 4.0).normalize().unwrap();
1903 let rot_matrix = q.to_rotation_matrix3x3();
1904 let [x1, y1, z1] = q.rotate_vector([1.0, 0.0, 0.0]);
1905 let [x2, y2, z2] = q.rotate_vector([0.0, 1.0, 0.0]);
1906 let [x3, y3, z3] = q.rotate_vector([0.0, 0.0, 1.0]);
1907 let expected = [
1908 x1, x2, x3, y1, y2, y3, z1, z2, z3,
1911 ];
1912 for (r, e) in rot_matrix.iter().zip(expected) {
1913 assert!((r - e).abs() <= f64::EPSILON);
1914 }
1915 }
1916
1917 #[cfg(any(feature = "std", feature = "libm"))]
1918 #[test]
1919 fn test_identity_matrix() {
1920 let identity: [[f32; 3]; 3] =
1922 [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1923 let q = UQ32::from_rotation_matrix3x3(&identity);
1924 let expected = UQ32::ONE;
1925 assert_eq!(q, expected);
1926 }
1927
1928 #[cfg(any(feature = "std", feature = "libm"))]
1929 #[test]
1930 fn test_rotation_x() {
1931 let angle = core::f32::consts::PI / 5.0;
1933 let rotation_x: [[f32; 3]; 3] = [
1934 [1.0, 0.0, 0.0],
1935 [0.0, angle.cos(), -angle.sin()],
1936 [0.0, angle.sin(), angle.cos()],
1937 ];
1938 let q = UQ32::from_rotation_matrix3x3(&rotation_x);
1939 let expected = UQ32::from_rotation_vector(&[angle, 0.0, 0.0]);
1940 assert!((q - expected).norm() <= f32::EPSILON);
1941 }
1942
1943 #[cfg(any(feature = "std", feature = "libm"))]
1944 #[test]
1945 fn test_rotation_y() {
1946 let angle = 4.0 * core::f32::consts::PI / 5.0;
1948 let rotation_y: [[f32; 3]; 3] = [
1949 [angle.cos(), 0.0, angle.sin()],
1950 [0.0, 1.0, 0.0],
1951 [-angle.sin(), 0.0, angle.cos()],
1952 ];
1953 let q = UQ32::from_rotation_matrix3x3(&rotation_y);
1954 let expected = UQ32::from_rotation_vector(&[0.0, angle, 0.0]);
1955 assert!((q - expected).norm() <= f32::EPSILON);
1956 }
1957
1958 #[cfg(any(feature = "std", feature = "libm"))]
1959 #[test]
1960 fn test_rotation_z() {
1961 let angle = 3.0 * core::f64::consts::PI / 5.0;
1963 let rotation_z: [[f64; 3]; 3] = [
1964 [angle.cos(), -angle.sin(), 0.0],
1965 [angle.sin(), angle.cos(), 0.0],
1966 [0.0, 0.0, 1.0],
1967 ];
1968 let q = UQ64::from_rotation_matrix3x3(&rotation_z);
1969 let expected = UQ64::from_rotation_vector(&[0.0, 0.0, angle]);
1970 assert!((q - expected).norm() <= f64::EPSILON);
1971 }
1972
1973 #[cfg(any(feature = "std", feature = "libm"))]
1974 #[test]
1975 fn test_arbitrary_rotation() {
1976 let arbitrary_rotation: [[f32; 3]; 3] =
1978 [[0.0, 0.0, 1.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1979 let q = UnitQuaternion::from_rotation_matrix3x3(&arbitrary_rotation);
1980 let expected = Q32::new(1.0, 1.0, 1.0, 1.0).normalize().unwrap();
1981 assert!((q - expected).norm() <= f32::EPSILON);
1982 }
1983
1984 #[cfg(any(feature = "std", feature = "libm"))]
1985 #[test]
1986 fn test_flat_array() {
1987 let angle = core::f32::consts::PI / 2.0;
1989 let rotation_z: [f32; 9] = [
1990 0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
1993 ];
1994 let q = UQ32::from_rotation_matrix3x3(&rotation_z);
1995 let expected = UQ32::from_rotation_vector(&[0.0, 0.0, angle]);
1996 assert!((q - expected).norm() <= f32::EPSILON);
1997 }
1998
1999 #[cfg(any(feature = "std", feature = "libm"))]
2000 #[test]
2001 fn test_from_rotation_to_rotation() {
2002 let mat = [
2004 0.36f64, 0.864, -0.352, 0.48, 0.152, 0.864, 0.8, -0.48, -0.36,
2005 ];
2006 let q = UnitQuaternion::from_rotation_matrix3x3(&mat);
2007 let restored_mat = q.to_rotation_matrix3x3();
2008 for (x, e) in restored_mat.iter().zip(mat) {
2009 assert!((x - e).abs() <= 4.0 * f64::EPSILON);
2010 }
2011 }
2012
2013 #[cfg(all(feature = "rand", any(feature = "std", feature = "libm")))]
2014 #[test]
2015 fn test_to_rotation_from_rotation() {
2016 use rand::Rng;
2019 let mut rng = make_seeded_rng();
2020 for _ in 0..100000 {
2021 let q = rng.random::<UQ32>();
2022 let mat = q.to_rotation_matrix3x3();
2023 let restored_q = UQ32::from_rotation_matrix3x3(&mat);
2024 assert!(restored_q.0.w >= 0.0);
2025 let expected = if q.0.w >= 0.0 { q } else { -q };
2026 if (restored_q - expected).norm() > 4.0 * f32::EPSILON {
2027 assert!((restored_q - expected).norm() <= 8.0 * f32::EPSILON);
2028 }
2029 }
2030 }
2031
2032 #[cfg(any(feature = "std", feature = "libm"))]
2033 #[test]
2034 fn test_zero_vector_a() {
2035 let a = [0.0, 0.0, 0.0];
2038 let b = [1.0, 0.0, 0.0];
2039 let q = UnitQuaternion::from_two_vectors(&a, &b);
2040 assert_eq!(q, UnitQuaternion::one());
2041 }
2042
2043 #[cfg(any(feature = "std", feature = "libm"))]
2044 #[test]
2045 fn test_zero_vector_b() {
2046 let a = [1.0, 0.0, 0.0];
2049 let b = [0.0, 0.0, 0.0];
2050 let q = UnitQuaternion::from_two_vectors(&a, &b);
2051 assert_eq!(q, UnitQuaternion::one());
2052 }
2053
2054 #[cfg(any(feature = "std", feature = "libm"))]
2055 #[test]
2056 fn test_parallel_vectors() {
2057 let a = [1.0, 0.0, 0.0];
2059 let b = [2.0, 0.0, 0.0];
2060 let q = UnitQuaternion::from_two_vectors(&a, &b);
2061 assert_eq!(q, UnitQuaternion::one());
2062 }
2063
2064 #[cfg(any(feature = "std", feature = "libm"))]
2065 #[test]
2066 fn test_opposite_vectors() {
2067 let a = [1.0, 0.0, 0.0];
2069 let b = [-1.0, 0.0, 0.0];
2070 let q = UnitQuaternion::from_two_vectors(&a, &b);
2071 assert_eq!(q.as_quaternion().w, 0.0);
2072 }
2073
2074 #[cfg(all(feature = "rand", any(feature = "std", feature = "libm")))]
2075 #[test]
2076 fn test_opposite_vectors_randomized() {
2077 use rand::Rng;
2080 let mut rng = make_seeded_rng();
2081 let mut gen_coord = move || rng.random::<f32>() * 2.0 - 1.0;
2082 for _ in 0..10000 {
2083 let a = [gen_coord(), gen_coord(), gen_coord()];
2084 let b = [-a[0], -a[1], -a[2]];
2085 let q = UnitQuaternion::from_two_vectors(&a, &b);
2086 assert!(q.as_quaternion().w.abs() <= f32::EPSILON);
2087 }
2088 }
2089
2090 #[cfg(any(feature = "std", feature = "libm"))]
2091 #[test]
2092 fn test_perpendicular_vectors() {
2093 let a = [1.0f32, 0.0, 0.0];
2096 let b = [0.0f32, 1.0, 0.0];
2097 let q = UQ32::from_two_vectors(&a, &b);
2098 let expected = Q32::new(1.0, 0.0, 0.0, 1.0).normalize().unwrap();
2099 assert!((q - expected).norm() <= 2.0 * f32::EPSILON);
2100 }
2101
2102 #[cfg(any(feature = "std", feature = "libm"))]
2103 #[test]
2104 fn test_non_normalized_vectors() {
2105 let a = [0.0, 3.0, 0.0];
2108 let b = [0.0, 5.0, 5.0];
2109 let q = UQ64::from_two_vectors(&a, &b);
2110 let expected =
2111 Q64::new(1.0, core::f64::consts::FRAC_PI_8.tan(), 0.0, 0.0)
2112 .normalize()
2113 .unwrap();
2114 assert!((q - expected).norm() <= 2.0 * f64::EPSILON);
2115
2116 let a = [0.0, 3.0, 0.0];
2117 let b = [0.0, -5.0, 5.0];
2118 let q = UQ64::from_two_vectors(&a, &b);
2119 let expected =
2120 Q64::new(1.0, (3.0 * core::f64::consts::FRAC_PI_8).tan(), 0.0, 0.0)
2121 .normalize()
2122 .unwrap();
2123 assert!((q - expected).norm() <= 2.0 * f64::EPSILON);
2124 }
2125
2126 #[cfg(any(feature = "std", feature = "libm"))]
2127 #[test]
2128 fn test_same_vector() {
2129 let a = [1.0, 1.0, 1.0];
2131 let q = UnitQuaternion::from_two_vectors(&a, &a);
2132 assert_eq!(q, UnitQuaternion::one());
2133 }
2134
2135 #[cfg(any(feature = "std", feature = "libm"))]
2136 #[test]
2137 fn test_arbitrary_vectors() {
2138 let a = [1.0, 2.0, 3.0];
2140 let b = [4.0, 5.0, 6.0];
2141 let q = UQ64::from_two_vectors(&a, &b);
2142 let v = [-3.0, 6.0, -3.0]; let v_norm = 54.0f64.sqrt();
2144 let dir = [v[0] / v_norm, v[1] / v_norm, v[2] / v_norm];
2145 let cos_angle = (a[0] * b[0] + a[1] * b[1] + a[2] * b[2])
2146 / ((a[0] * a[0] + a[1] * a[1] + a[2] * a[2])
2147 * (b[0] * b[0] + b[1] * b[1] + b[2] * b[2]))
2148 .sqrt();
2149 let angle = cos_angle.acos();
2150 let expected = UQ64::from_rotation_vector(&[
2151 dir[0] * angle,
2152 dir[1] * angle,
2153 dir[2] * angle,
2154 ]);
2155 assert!((q - expected).norm() <= 2.0 * f64::EPSILON);
2156 }
2157
2158 #[cfg(all(feature = "rand", any(feature = "std", feature = "libm")))]
2159 #[test]
2160 fn test_from_to_vectors_randomized() {
2161 use rand::Rng;
2163 let mut rng = make_seeded_rng();
2164 let mut gen_coord = move || rng.random::<f32>() * 2.0 - 1.0;
2165 for _ in 0..100000 {
2166 let a = [gen_coord(), gen_coord(), gen_coord()];
2167 let b = [gen_coord(), gen_coord(), gen_coord()];
2168 let q = UQ32::from_two_vectors(&a, &b);
2169 let rotated_a = q.rotate_vector(a);
2170 let dot =
2171 rotated_a[0] * b[0] + rotated_a[1] * b[1] + rotated_a[2] * b[2];
2172 let b_norm_sqr = b[0] * b[0] + b[1] * b[1] + b[2] * b[2];
2173 let a_norm_sqr = a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
2174 let expected = (a_norm_sqr * b_norm_sqr).sqrt();
2175 let cos_angle = dot / expected;
2176 assert!((cos_angle - 1.0).abs() <= 8.0 * f32::EPSILON);
2177 }
2178 }
2179
2180 #[test]
2181 fn test_default_unit_quaternion() {
2182 assert_eq!(UQ32::default().into_quaternion(), Q32::ONE);
2184 }
2185
2186 #[test]
2187 fn test_constant_one() {
2188 assert_eq!(UQ32::ONE.into_quaternion(), Q32::ONE);
2190 assert_eq!(
2191 UnitQuaternion::<i32>::ONE.into_quaternion(),
2192 Quaternion::<i32>::ONE
2193 );
2194 }
2195
2196 #[test]
2197 fn test_constant_i() {
2198 assert_eq!(UQ32::I.into_quaternion(), Q32::I);
2201 }
2202
2203 #[test]
2204 fn test_constant_j() {
2205 assert_eq!(UQ32::J.into_quaternion(), Q32::J);
2208 }
2209
2210 #[test]
2211 fn test_constant_k() {
2212 assert_eq!(UQ32::K.into_quaternion(), Q32::K);
2215 }
2216
2217 #[test]
2218 fn test_const_one() {
2219 assert_eq!(<UQ32 as ConstOne>::ONE.into_quaternion(), Q32::ONE);
2221 }
2222
2223 #[test]
2224 fn test_one_trait() {
2225 assert_eq!(<UQ32 as One>::one().into_quaternion(), Q32::ONE);
2227 assert!(UQ64::ONE.is_one());
2228 assert!(!UQ64::I.is_one());
2229 assert!(!UQ64::J.is_one());
2230 assert!(!UQ64::K.is_one());
2231 let mut uq = UQ32::I;
2232 uq.set_one();
2233 assert!(uq.is_one());
2234 }
2235
2236 #[test]
2237 fn test_one_func() {
2238 assert_eq!(UQ32::one().into_quaternion(), Q32::ONE);
2240 }
2241
2242 #[test]
2243 fn test_unit_quaternion_i_func() {
2244 assert_eq!(UQ32::i().into_quaternion(), Q32::i());
2246 }
2247
2248 #[test]
2249 fn test_unit_quaternion_j_func() {
2250 assert_eq!(UQ32::j().into_quaternion(), Q32::j());
2252 }
2253
2254 #[test]
2255 fn test_unit_quaternion_k_func() {
2256 assert_eq!(UQ32::k().into_quaternion(), Q32::k());
2258 }
2259
2260 #[test]
2261 fn test_into_quaternion() {
2262 assert_eq!(UQ32::ONE.into_quaternion(), Q32::ONE);
2265 assert_eq!(UQ32::I.into_quaternion(), Q32::I);
2266 assert_eq!(UQ32::J.into_quaternion(), Q32::J);
2267 assert_eq!(UQ32::K.into_quaternion(), Q32::K);
2268 }
2269
2270 #[test]
2271 fn test_into_inner() {
2272 assert_eq!(UQ32::ONE.into_inner(), Q32::ONE);
2275 assert_eq!(UQ32::I.into_inner(), Q32::I);
2276 assert_eq!(UQ32::J.into_inner(), Q32::J);
2277 assert_eq!(UQ32::K.into_inner(), Q32::K);
2278 }
2279
2280 #[test]
2281 fn test_as_quaternion() {
2282 assert_eq!(UQ32::ONE.as_quaternion(), &Q32::ONE);
2285 assert_eq!(UQ32::I.as_quaternion(), &Q32::I);
2286 assert_eq!(UQ32::J.as_quaternion(), &Q32::J);
2287 assert_eq!(UQ32::K.as_quaternion(), &Q32::K);
2288 }
2289
2290 #[test]
2291 fn test_borrow() {
2292 assert_eq!(<UQ32 as Borrow<Q32>>::borrow(&UQ32::ONE), &Q32::ONE);
2295 assert_eq!(<UQ32 as Borrow<Q32>>::borrow(&UQ32::I), &Q32::I);
2296 assert_eq!(<UQ32 as Borrow<Q32>>::borrow(&UQ32::J), &Q32::J);
2297 assert_eq!(<UQ32 as Borrow<Q32>>::borrow(&UQ32::K), &Q32::K);
2298 }
2299
2300 #[test]
2301 fn test_unit_quaternion_conj() {
2302 assert_eq!(UQ32::ONE.conj(), UQ32::ONE);
2304 assert_eq!(UQ64::I.conj(), -UQ64::I);
2305 assert_eq!(UQ32::J.conj(), -UQ32::J);
2306 assert_eq!(UQ64::K.conj(), -UQ64::K);
2307 }
2308
2309 #[cfg(any(feature = "std", feature = "libm"))]
2310 #[test]
2311 fn test_unit_quaternion_conj_with_normalize() {
2312 assert_eq!(
2314 Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().conj(),
2315 Q32::new(1.0, -2.0, -3.0, -4.0).normalize().unwrap()
2316 )
2317 }
2318
2319 #[cfg(any(feature = "std", feature = "libm"))]
2320 #[test]
2321 fn test_unit_quaternion_inv_func() {
2322 assert_eq!(
2324 UQ32::inv(&Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap()),
2325 Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().conj()
2326 )
2327 }
2328
2329 #[cfg(any(feature = "std", feature = "libm"))]
2330 #[test]
2331 fn test_unit_quaternion_inv_trait() {
2332 assert_eq!(
2334 <UQ32 as Inv>::inv(
2335 Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap()
2336 ),
2337 Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().conj()
2338 )
2339 }
2340
2341 #[cfg(any(feature = "std", feature = "libm"))]
2342 #[test]
2343 fn test_unit_quaternion_ref_inv_trait() {
2344 assert_eq!(
2346 <&UQ32 as Inv>::inv(
2347 &Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap()
2348 ),
2349 Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().conj()
2350 )
2351 }
2352
2353 #[test]
2354 fn test_unit_quaternion_neg() {
2355 assert_eq!(
2357 (-UQ32::ONE).into_quaternion(),
2358 Q32::new(-1.0, 0.0, 0.0, 0.0)
2359 );
2360 assert_eq!((-UQ32::I).into_quaternion(), Q32::new(0.0, -1.0, 0.0, 0.0));
2361 assert_eq!((-UQ32::J).into_quaternion(), Q32::new(0.0, 0.0, -1.0, 0.0));
2362 assert_eq!((-UQ32::K).into_quaternion(), Q32::new(0.0, 0.0, 0.0, -1.0));
2363 }
2364
2365 #[cfg(any(feature = "std", feature = "libm"))]
2366 #[test]
2367 fn test_unit_quaternion_adjust_norm() {
2368 let mut q = UQ32::from_euler_angles(1.0, 0.5, 1.5);
2370 for _ in 0..25 {
2371 q = q * q;
2372 }
2373 assert!((q.into_quaternion().norm() - 1.0).abs() > 0.5);
2374 assert!(
2375 (q.adjust_norm().into_quaternion().norm() - 1.0).abs()
2376 <= 2.0 * f32::EPSILON
2377 );
2378 }
2379
2380 #[test]
2381 fn test_unit_quaternion_rotate_vector_units() {
2382 let v = [1.0, 2.0, 3.0];
2384 assert_eq!(UQ32::I.rotate_vector(v), [1.0, -2.0, -3.0]);
2385 assert_eq!(UQ32::J.rotate_vector(v), [-1.0, 2.0, -3.0]);
2386 assert_eq!(UQ32::K.rotate_vector(v), [-1.0, -2.0, 3.0]);
2387 }
2388
2389 #[cfg(any(feature = "std", feature = "libm"))]
2390 #[test]
2391 fn test_unit_quaternion_rotate_vector_normalized() {
2392 let q = Q32::new(1.0, 1.0, 1.0, 1.0).normalize().unwrap();
2394 let v = [1.0, 2.0, 3.0];
2395 let result = q.rotate_vector(v);
2396 assert_eq!(result, [3.0, 1.0, 2.0]);
2397 }
2398
2399 #[cfg(any(feature = "std", feature = "libm"))]
2401 fn generate_unit_quaternion_data() -> impl Iterator<Item = UQ32> {
2402 [
2403 UQ32::ONE,
2404 UQ32::I,
2405 UQ32::J,
2406 UQ32::K,
2407 Q32::new(1.0, 1.0, 1.0, 1.0).normalize().unwrap(),
2408 Q32::new(10.0, 1.0, 1.0, 1.0).normalize().unwrap(),
2409 Q32::new(1.0, 10.0, 1.0, 1.0).normalize().unwrap(),
2410 Q32::new(1.0, 1.0, 3.0, 4.0).normalize().unwrap(),
2411 Q32::new(1.0, -1.0, 3.0, -4.0).normalize().unwrap(),
2412 ]
2413 .into_iter()
2414 }
2415
2416 #[cfg(any(feature = "std", feature = "libm"))]
2417 #[test]
2418 fn test_slerp_t_zero() {
2419 for q1 in generate_unit_quaternion_data() {
2422 for q2 in generate_unit_quaternion_data() {
2423 let result = q1.slerp(&q2, 0.0);
2424 assert!((result - q1).norm() <= f32::EPSILON);
2425 }
2426 }
2427 }
2428
2429 #[cfg(any(feature = "std", feature = "libm"))]
2430 #[test]
2431 fn test_slerp_t_one() {
2432 use core::cmp::Ordering;
2435
2436 for q1 in generate_unit_quaternion_data() {
2437 for q2 in generate_unit_quaternion_data() {
2438 let result = q1.slerp(&q2, 1.0);
2439 match q1.dot(q2).partial_cmp(&0.0) {
2440 Some(Ordering::Greater) => {
2441 assert!((result - q2).norm() <= f32::EPSILON)
2442 }
2443 Some(Ordering::Less) => {
2444 assert!((result + q2).norm() <= f32::EPSILON)
2445 }
2446 _ => {}
2447 }
2448 }
2449 }
2450 }
2451
2452 #[cfg(any(feature = "std", feature = "libm"))]
2453 #[test]
2454 fn test_slerp_t_half() {
2455 use core::cmp::Ordering;
2458
2459 for q1 in generate_unit_quaternion_data() {
2460 for q2 in generate_unit_quaternion_data() {
2461 let result = q1.slerp(&q2, 0.5);
2462 let dot_sign = match q1.dot(q2).partial_cmp(&0.0) {
2463 Some(Ordering::Greater) => 1.0,
2464 Some(Ordering::Less) => -1.0,
2465 _ => continue, };
2467 assert!(
2468 (result - (q1 + dot_sign * q2).normalize().unwrap()).norm()
2469 <= f32::EPSILON
2470 )
2471 }
2472 }
2473 }
2474
2475 #[cfg(any(feature = "std", feature = "libm"))]
2476 #[test]
2477 fn test_slerp_small_angle() {
2478 let q1 = UQ32::ONE;
2481 let q2 = Q32::new(999_999.0, 1.0, 0.0, 0.0).normalize().unwrap();
2482 let t = 0.5;
2483 let result = q1.slerp(&q2, t);
2484 let expected = Q32::new(999_999.75, 0.5, 0.0, 0.0).normalize().unwrap();
2485 assert!((result - expected).norm() <= f32::EPSILON);
2486 }
2487
2488 #[cfg(any(feature = "std", feature = "libm"))]
2489 #[test]
2490 fn test_sqrt_of_identity() {
2491 assert_eq!(UQ32::ONE.sqrt(), UQ32::ONE);
2493 }
2494
2495 #[cfg(any(feature = "std", feature = "libm"))]
2496 #[test]
2497 fn test_sqrt_of_negative_identity() {
2498 let q = Q64::new(-1.0, 0.0, -0.0, -0.0).normalize().unwrap();
2500 assert_eq!(q.sqrt(), UQ64::I);
2501 assert!(q.sqrt().0.w.is_sign_positive());
2502 assert!(q.sqrt().0.y.is_sign_negative());
2503 assert!(q.sqrt().0.z.is_sign_negative());
2504
2505 let q = Q64::new(-1.0, -0.0, 0.0, 0.0).normalize().unwrap();
2506 assert_eq!(q.sqrt(), -UQ64::I);
2507 assert!(q.sqrt().0.w.is_sign_positive());
2508 assert!(q.sqrt().0.y.is_sign_positive());
2509 assert!(q.sqrt().0.z.is_sign_positive());
2510 }
2511
2512 #[cfg(any(feature = "std", feature = "libm"))]
2513 #[test]
2514 fn test_sqrt_general_case() {
2515 let c = Q64::new(1.0, 2.0, -3.0, 4.0).normalize().unwrap();
2517 let q = c.sqrt();
2518 assert!((q * q - c).norm() <= f64::EPSILON);
2519 }
2520
2521 #[cfg(any(feature = "std", feature = "libm"))]
2522 #[test]
2523 fn test_sqrt_with_negative_real_part() {
2524 let c = Q64::new(-4.0, 2.0, -3.0, 1.0).normalize().unwrap();
2526 let q = c.sqrt();
2527 assert!((q * q - c).norm() <= f64::EPSILON);
2528 }
2529
2530 #[cfg(any(feature = "std", feature = "libm"))]
2531 #[test]
2532 fn test_sqrt_with_subnormal_imaginary_parts() {
2533 let min_positive = f64::MIN_POSITIVE;
2535 let q = Quaternion::new(-1.0, min_positive, min_positive, min_positive)
2536 .normalize()
2537 .unwrap();
2538 let result = q.sqrt();
2539 let expected = Quaternion::new(
2540 min_positive * 0.75f64.sqrt(),
2541 (1.0f64 / 3.0).sqrt(),
2542 (1.0f64 / 3.0).sqrt(),
2543 (1.0f64 / 3.0).sqrt(),
2544 )
2545 .normalize()
2546 .unwrap();
2547 assert!(
2548 (result.0.w - expected.0.w).abs()
2549 <= 2.0 * expected.0.w * f64::EPSILON
2550 );
2551 assert!(
2552 (result.0.x - expected.0.x).abs()
2553 <= 2.0 * expected.0.x * f64::EPSILON
2554 );
2555 assert!(
2556 (result.0.y - expected.0.y).abs()
2557 <= 2.0 * expected.0.y * f64::EPSILON
2558 );
2559 assert!(
2560 (result.0.z - expected.0.z).abs()
2561 <= 2.0 * expected.0.z * f64::EPSILON
2562 );
2563 }
2564
2565 #[cfg(any(feature = "std", feature = "libm"))]
2566 #[cfg(feature = "unstable")]
2567 #[test]
2568 fn test_ln_of_identity() {
2569 assert_eq!(UQ32::ONE.ln(), PureQuaternion::ZERO);
2571 }
2572
2573 #[cfg(any(feature = "std", feature = "libm"))]
2574 #[cfg(feature = "unstable")]
2575 #[test]
2576 fn test_ln_of_normal_case() {
2577 let q = Q64::new(1.0, 2.0, 3.0, 4.0);
2579 let p = q.normalize().expect("Failed to normalize quaternion").ln();
2580 assert!((p.z / p.x - q.z / q.x).abs() <= 4.0 * f64::EPSILON);
2581 assert!((p.y / p.x - q.y / q.x).abs() <= 4.0 * f64::EPSILON);
2582 assert!((p.norm() - 29.0f64.sqrt().atan()).abs() <= 4.0 * f64::EPSILON);
2583 }
2584
2585 #[cfg(any(feature = "std", feature = "libm"))]
2586 #[cfg(feature = "unstable")]
2587 #[test]
2588 fn test_ln_near_positive_real_axis() {
2589 let q = Quaternion::new(1.0, 1e-10, 1e-10, 1e-10)
2591 .normalize()
2592 .unwrap();
2593 let ln_q = q.ln();
2594 let expected = PureQuaternion::new(1e-10, 1e-10, 1e-10); assert!((ln_q - expected).norm() <= 1e-11);
2596 }
2597
2598 #[cfg(any(feature = "std", feature = "libm"))]
2599 #[cfg(feature = "unstable")]
2600 #[test]
2601 fn test_ln_negative_real_axis() {
2602 let q = Q32::new(-1.0, 0.0, 0.0, 0.0).normalize().unwrap();
2604 let ln_q = q.ln();
2605 let expected = PureQuaternion::new(core::f32::consts::PI, 0.0, 0.0); assert!(
2607 (ln_q - expected).norm() <= core::f32::consts::PI * f32::EPSILON
2608 );
2609 }
2610
2611 #[cfg(any(feature = "std", feature = "libm"))]
2612 #[cfg(feature = "unstable")]
2613 #[test]
2614 fn test_ln_near_negative_real_axis() {
2615 use core::f32;
2617 let q = Q32::new(-2.0, 346.0 * f32::EPSILON, 0.0, 0.0);
2618 let uq = q.normalize().unwrap();
2619 let ln_uq = uq.ln();
2620 let expected =
2621 PureQuaternion::new(f32::consts::PI + q.x / q.w, 0.0, 0.0);
2622 assert!((ln_uq - expected).norm() <= 8.0 * f32::EPSILON);
2623
2624 let q = Q32::new(-1.0, f32::MIN_POSITIVE / 192.0, 0.0, 0.0);
2625 let uq = q.normalize().unwrap();
2626 let ln_uq = uq.ln();
2627 let expected = PureQuaternion::new(f32::consts::PI, 0.0, 0.0);
2628 assert_eq!(ln_uq, expected);
2629 }
2630
2631 #[cfg(all(feature = "serde", any(feature = "std", feature = "libm")))]
2632 #[test]
2633 fn test_serde_unit_quaternion() {
2634 let q = Q64::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
2636
2637 let serialized =
2639 serde_json::to_string(&q).expect("Failed to serialize quaternion");
2640
2641 let deserialized: UQ64 = serde_json::from_str(&serialized)
2643 .expect("Failed to deserialize quaternion");
2644
2645 assert_eq!(q, deserialized);
2647 }
2648
2649 #[cfg(feature = "serde")]
2650 #[test]
2651 fn test_serde_unit_quaternion_k() {
2652 let q = UQ64::K;
2654
2655 let serialized =
2657 serde_json::to_string(&q).expect("Failed to serialize quaternion");
2658
2659 let deserialized: UQ64 = serde_json::from_str(&serialized)
2661 .expect("Failed to deserialize quaternion");
2662
2663 assert_eq!(q, deserialized);
2665 }
2666
2667 #[cfg(all(feature = "rand", any(feature = "std", feature = "libm")))]
2668 fn make_seeded_rng() -> impl rand::Rng {
2669 use rand::SeedableRng;
2670 rand::rngs::SmallRng::seed_from_u64(0x7F0829AE4D31C6B5)
2671 }
2672
2673 #[cfg(all(feature = "rand", any(feature = "std", feature = "libm")))]
2674 #[test]
2675 fn test_unit_quaternion_sample_six_sigma() {
2676 use rand::distr::{Distribution, StandardUniform};
2678 let num_iters = 1_000_000;
2679 let mut sum: Q64 = num_traits::Zero::zero();
2680 let rng = make_seeded_rng();
2681 for q in Distribution::<UQ64>::sample_iter(StandardUniform, rng)
2682 .take(num_iters)
2683 {
2684 sum += q;
2685 }
2686
2687 let sum_std_dev = (num_iters as f64).sqrt();
2688 assert!(sum.norm() < 6.0 * sum_std_dev);
2691 }
2692
2693 #[cfg(all(feature = "rand", any(feature = "std", feature = "libm")))]
2694 #[test]
2695 fn test_unit_quaternion_sample_half_planes() {
2696 use rand::{
2698 distr::{Distribution, StandardUniform},
2699 Rng,
2700 };
2701 let num_iters = 1_000_000;
2702 let mut rng = make_seeded_rng();
2703 const NUM_DIRS: usize = 10;
2704 let dirs: [UQ64; NUM_DIRS] = [
2705 UQ64::ONE,
2706 UQ64::I,
2707 UQ64::J,
2708 UQ64::K,
2709 Q64::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap(),
2710 Q64::new(4.0, -3.0, 2.0, -1.0).normalize().unwrap(),
2711 rng.random(),
2712 rng.random(),
2713 rng.random(),
2714 rng.random(),
2715 ];
2716 let mut counters = [0; NUM_DIRS];
2717 for q in Distribution::<UQ64>::sample_iter(StandardUniform, rng)
2718 .take(num_iters)
2719 {
2720 for (dir, counter) in dirs.iter().zip(counters.iter_mut()) {
2721 if q.dot(*dir) > 0.0 {
2722 *counter += 1;
2723 }
2724 }
2725 }
2726
2727 let six_sigma = 3 * (num_iters as f64).sqrt() as i32;
2728 let expected_count = num_iters as i32 / 2;
2729 for counter in counters {
2732 assert!((counter - expected_count).abs() < six_sigma);
2733 }
2734 }
2735}