1use serde::{Deserialize, Serialize};
9
10use super::CoordinateSystem;
11use crate::{
12 dual::Dual,
13 geop_error::{GeopError, GeopResult},
14 scalars::{Number, Scalar},
15 solvers::{least_squares::Rows, system::Variable},
16 vector::{Vector, Vector3},
17};
18
19const SERIES_TERMS: usize = 12;
22
23#[derive(Clone, Copy, Debug, PartialEq)]
25pub struct Quaternion<T = Scalar> {
26 pub w: T,
27 pub x: T,
28 pub y: T,
29 pub z: T,
30}
31
32impl<T: Number> Quaternion<T> {
33 pub fn new(w: T, x: T, y: T, z: T) -> Self {
34 Self { w, x, y, z }
35 }
36
37 pub fn identity() -> Self {
38 Self::new(T::ONE, T::ZERO, T::ZERO, T::ZERO)
39 }
40
41 pub fn is_identity(&self) -> bool {
44 let exactly = |c: T, value: f64| c.is_sharp() && c.to_f64() == value;
45 exactly(self.w, 1.0) && exactly(self.x, 0.0) && exactly(self.y, 0.0) && exactly(self.z, 0.0)
46 }
47
48 pub fn pure(v: &Vector3<T>) -> Self {
50 Self::new(T::ZERO, v[0], v[1], v[2])
51 }
52
53 pub fn vector(&self) -> Vector3<T> {
55 Vector3::from_array([self.x, self.y, self.z])
56 }
57
58 pub fn components(&self) -> [T; 4] {
60 [self.w, self.x, self.y, self.z]
61 }
62
63 pub fn map<U: Number>(&self, f: impl Fn(T) -> U) -> Quaternion<U> {
65 Quaternion::new(f(self.w), f(self.x), f(self.y), f(self.z))
66 }
67
68 pub fn mul(&self, o: &Self) -> Self {
69 Self {
70 w: self
71 .w
72 .mul(o.w)
73 .sub(self.x.mul(o.x))
74 .sub(self.y.mul(o.y))
75 .sub(self.z.mul(o.z)),
76 x: self
77 .w
78 .mul(o.x)
79 .add(self.x.mul(o.w))
80 .add(self.y.mul(o.z))
81 .sub(self.z.mul(o.y)),
82 y: self
83 .w
84 .mul(o.y)
85 .sub(self.x.mul(o.z))
86 .add(self.y.mul(o.w))
87 .add(self.z.mul(o.x)),
88 z: self
89 .w
90 .mul(o.z)
91 .add(self.x.mul(o.y))
92 .sub(self.y.mul(o.x))
93 .add(self.z.mul(o.w)),
94 }
95 }
96
97 pub fn conj(&self) -> Self {
98 Self::new(self.w, self.x.neg(), self.y.neg(), self.z.neg())
99 }
100
101 pub fn scale(&self, s: T) -> Self {
102 self.map(|c| c.mul(s))
103 }
104
105 pub fn add(&self, o: &Self) -> Self {
106 Self::new(
107 self.w.add(o.w),
108 self.x.add(o.x),
109 self.y.add(o.y),
110 self.z.add(o.z),
111 )
112 }
113
114 pub fn dot(&self, o: &Self) -> T {
115 self.w
116 .mul(o.w)
117 .add(self.x.mul(o.x))
118 .add(self.y.mul(o.y))
119 .add(self.z.mul(o.z))
120 }
121
122 pub fn norm_sq(&self) -> T {
123 self.dot(self)
124 }
125
126 pub fn normalized(&self) -> GeopResult<Self> {
129 let n = self.norm_sq();
130 if !n.definitely_greater(T::ZERO) {
131 return Err(GeopError::new("a rotation cannot be the zero quaternion"));
132 }
133 Ok(self.scale(T::ONE.div(n.sqrt()?)?))
134 }
135
136 pub fn from_euler(degrees: [T; 3]) -> GeopResult<Self> {
138 let half_radians = T::PI.div(T::from_i64(360))?;
139 let [a, b, c] = degrees.map(|d| d.mul(half_radians));
140 let qx = Self::new(a.cos(), a.sin(), T::ZERO, T::ZERO);
141 let qy = Self::new(b.cos(), T::ZERO, b.sin(), T::ZERO);
142 let qz = Self::new(c.cos(), T::ZERO, T::ZERO, c.sin());
143 Ok(qz.mul(&qy.mul(&qx)))
144 }
145
146 pub fn from_rotation_columns(columns: [Vector3<T>; 3]) -> GeopResult<Self> {
153 let m = |r: usize, c: usize| columns[c][r];
154 let one = T::ONE;
155 let trace = m(0, 0).add(m(1, 1)).add(m(2, 2));
156 let size = |v: T| v.to_f64();
157 let candidates = [trace, m(0, 0), m(1, 1), m(2, 2)];
158 let largest = (1..4).fold(0, |best, k| {
159 if size(candidates[k]) > size(candidates[best]) {
160 k
161 } else {
162 best
163 }
164 });
165 let quarter = T::ONE.div(T::from_i64(4))?;
166 let q = match largest {
167 0 => {
168 let s = one.add(trace).sqrt()?.mul(T::TWO);
169 let inv = one.div(s)?;
170 Self::new(
171 s.mul(quarter),
172 m(2, 1).sub(m(1, 2)).mul(inv),
173 m(0, 2).sub(m(2, 0)).mul(inv),
174 m(1, 0).sub(m(0, 1)).mul(inv),
175 )
176 }
177 1 => {
178 let s = one
179 .add(m(0, 0))
180 .sub(m(1, 1))
181 .sub(m(2, 2))
182 .sqrt()?
183 .mul(T::TWO);
184 let inv = one.div(s)?;
185 Self::new(
186 m(2, 1).sub(m(1, 2)).mul(inv),
187 s.mul(quarter),
188 m(0, 1).add(m(1, 0)).mul(inv),
189 m(0, 2).add(m(2, 0)).mul(inv),
190 )
191 }
192 2 => {
193 let s = one
194 .add(m(1, 1))
195 .sub(m(0, 0))
196 .sub(m(2, 2))
197 .sqrt()?
198 .mul(T::TWO);
199 let inv = one.div(s)?;
200 Self::new(
201 m(0, 2).sub(m(2, 0)).mul(inv),
202 m(0, 1).add(m(1, 0)).mul(inv),
203 s.mul(quarter),
204 m(1, 2).add(m(2, 1)).mul(inv),
205 )
206 }
207 _ => {
208 let s = one
209 .add(m(2, 2))
210 .sub(m(0, 0))
211 .sub(m(1, 1))
212 .sqrt()?
213 .mul(T::TWO);
214 let inv = one.div(s)?;
215 Self::new(
216 m(1, 0).sub(m(0, 1)).mul(inv),
217 m(0, 2).add(m(2, 0)).mul(inv),
218 m(1, 2).add(m(2, 1)).mul(inv),
219 s.mul(quarter),
220 )
221 }
222 };
223 q.normalized()
224 .map_err(|e| e.with_context(format!("from_rotation_columns({columns:?})")))
225 }
226
227 pub fn rotation_columns(&self) -> GeopResult<[Vector3<T>; 3]> {
231 let n = T::ONE.div(self.norm_sq())?;
232 let two = T::TWO.mul(n);
233 let Self { w, x, y, z } = *self;
234 let (xx, yy, zz) = (x.mul(x), y.mul(y), z.mul(z));
235 let (xy, xz, yz) = (x.mul(y), x.mul(z), y.mul(z));
236 let (wx, wy, wz) = (w.mul(x), w.mul(y), w.mul(z));
237 let one = T::ONE;
238 let v = |a: T, b: T, c: T| Vector3::from_array([a, b, c]);
239 Ok([
240 v(
241 one.sub(two.mul(yy.add(zz))),
242 two.mul(xy.add(wz)),
243 two.mul(xz.sub(wy)),
244 ),
245 v(
246 two.mul(xy.sub(wz)),
247 one.sub(two.mul(xx.add(zz))),
248 two.mul(yz.add(wx)),
249 ),
250 v(
251 two.mul(xz.add(wy)),
252 two.mul(yz.sub(wx)),
253 one.sub(two.mul(xx.add(yy))),
254 ),
255 ])
256 }
257}
258
259#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
274#[serde(into = "PoseFile", try_from = "PoseFile", bound = "T: Number")]
275pub struct Pose<T: Number = Scalar> {
276 rotation: Quaternion<T>,
277 position: Vector3<T>,
278}
279
280#[derive(Serialize, Deserialize)]
282struct PoseFile {
283 position: [f64; 3],
284 rotation: [f64; 4],
285}
286
287impl<T: Number> From<Pose<T>> for PoseFile {
288 fn from(pose: Pose<T>) -> Self {
289 let p = pose.position();
290 PoseFile {
291 position: p.to_f64_array(),
292 rotation: pose.rotation().components().map(|c| c.to_f64()),
293 }
294 }
295}
296
297impl<T: Number> TryFrom<PoseFile> for Pose<T> {
298 type Error = GeopError;
299
300 fn try_from(file: PoseFile) -> GeopResult<Self> {
305 let [w, x, y, z] = file.rotation.map(T::from_f64);
306 let rotation = Quaternion::new(w, x, y, z);
307 if !rotation.norm_sq().definitely_greater(T::ZERO) {
308 return Err(GeopError::new("a rotation cannot be the zero quaternion"));
309 }
310 Ok(Pose {
311 rotation,
312 position: Vector3::from_array(file.position.map(T::from_f64)),
313 })
314 }
315}
316
317impl crate::exact::Exact for Pose {
321 fn write<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
322 let [w, x, y, z] = self.rotation.components();
323 let rotation = Vector::from_array([w, x, y, z]);
324 let written = (
325 crate::exact::Written(&self.position),
326 crate::exact::Written(&rotation),
327 );
328 written.serialize(serializer)
329 }
330
331 fn read<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
332 let (crate::exact::Read(position), crate::exact::Read(rotation)): (
333 crate::exact::Read<Vector3>,
334 crate::exact::Read<Vector<4>>,
335 ) = Deserialize::deserialize(deserializer)?;
336 Ok(Pose {
337 rotation: Quaternion::new(rotation[0], rotation[1], rotation[2], rotation[3]),
338 position,
339 })
340 }
341}
342
343impl<T: Number> Default for Pose<T> {
344 fn default() -> Self {
345 Pose::identity()
346 }
347}
348
349impl<T: Number> Pose<T> {
350 pub fn identity() -> Self {
351 Pose {
352 rotation: Quaternion::identity(),
353 position: Vector3::zero(),
354 }
355 }
356
357 pub fn new(position: Vector3<T>, rotation: Quaternion<T>) -> GeopResult<Self> {
360 Ok(Pose {
361 rotation: rotation.normalized()?,
362 position,
363 })
364 }
365
366 pub fn from_euler(position: Vector3<T>, degrees: [T; 3]) -> GeopResult<Self> {
369 Pose::new(position, Quaternion::from_euler(degrees)?)
370 }
371
372 pub fn saved(&self) -> GeopResult<Self> {
376 Pose::try_from(PoseFile::from(*self))
377 }
378
379 pub fn position(&self) -> Vector3<T> {
381 self.position
382 }
383
384 pub fn rotation(&self) -> Quaternion<T> {
388 self.rotation
389 }
390
391 pub fn map<U: Number>(&self, f: impl Fn(T) -> U) -> Pose<U> {
393 Pose {
394 rotation: self.rotation.map(&f),
395 position: self.position.map(&f),
396 }
397 }
398
399 pub fn euler_degrees(&self) -> [f64; 3] {
405 let q = self.rotation().components().map(|c| c.to_f64());
407 let norm = q.iter().map(|c| c * c).sum::<f64>().sqrt();
408 let [w, x, y, z] = q.map(|c| c / norm);
409 let (r00, r10, r20) = (
411 1.0 - 2.0 * (y * y + z * z),
412 2.0 * (x * y + w * z),
413 2.0 * (x * z - w * y),
414 );
415 let r21 = 2.0 * (y * z + w * x);
416 let (r01, r11) = (2.0 * (x * y - w * z), 1.0 - 2.0 * (x * x + z * z));
417 let c = r10.atan2(r00);
421 let (sc, cc) = c.sin_cos();
422 let b = (-r20).atan2(r00 * cc + r10 * sc);
423 let (sb, cb) = b.sin_cos();
425 let m11 = -sc * r01 + cc * r11;
426 let m21 = sb * (cc * r01 + sc * r11) + cb * r21;
427 let a = m21.atan2(m11);
428 [a, b, c].map(f64::to_degrees)
429 }
430
431 pub fn with_position(&self, position: Vector3<T>) -> Pose<T> {
433 Pose {
434 rotation: self.rotation,
435 position,
436 }
437 }
438
439 pub fn compose(&self, inner: &Pose<T>) -> Pose<T> {
441 Pose {
442 rotation: self.rotation.mul(&inner.rotation),
443 position: self.apply(&inner.position),
444 }
445 }
446
447 pub fn inverse(&self) -> Pose<T> {
452 let conj = self.rotation.conj();
453 let turned = conj
454 .mul(&Quaternion::pure(&self.position))
455 .mul(&self.rotation);
456 Pose {
457 rotation: self.unturned(conj),
458 position: self.unturned(turned).vector().neg(),
459 }
460 }
461
462 pub fn exp(omega: &Vector3<T>, v: &Vector3<T>) -> GeopResult<Self> {
481 let phi2 = omega.prod_dot(omega).div(T::from_i64(4))?;
482 let (cos, s, c) = if phi2.definitely_less(T::ONE) {
483 let minus = phi2.neg();
487 let (mut cos, mut s, mut c) = (T::ZERO, T::ZERO, T::ZERO);
488 let mut cos_term = T::ONE;
490 let mut s_term = T::ONE.div(T::TWO)?;
491 let mut d = T::from_i64(2).div(T::from_i64(48))?;
492 for k in 0..SERIES_TERMS {
493 cos = cos.add(cos_term);
494 s = s.add(s_term);
495 c = c.sub(d);
496 let k = k as i64;
497 cos_term = cos_term
498 .mul(minus)
499 .div(T::from_i64((2 * k + 1) * (2 * k + 2)))?;
500 s_term = s_term
501 .mul(minus)
502 .div(T::from_i64((2 * k + 2) * (2 * k + 3)))?;
503 d = d
504 .mul(minus)
505 .mul(T::from_i64(k + 2))
506 .div(T::from_i64((k + 1) * (2 * k + 4) * (2 * k + 5)))?;
507 }
508 let within = T::ONE.neg().union(T::ONE);
514 (
515 cos.add(cos_term.mul(within)),
516 s.add(s_term.mul(within)),
517 c.sub(d.mul(within)),
518 )
519 } else {
520 let phi = phi2.sqrt()?;
521 let (cos, sin) = (phi.cos(), phi.sin());
522 let s = sin.div(T::TWO.mul(phi))?;
523 let c = cos.sub(sin.div(phi)?).div(T::from_i64(8).mul(phi2))?;
524 (cos, s, c)
525 };
526 let rotation = Quaternion::new(cos, s.mul(omega[0]), s.mul(omega[1]), s.mul(omega[2]));
527 let (two_s_cos, s2) = (T::TWO.mul(s).mul(cos), s.mul(s));
528 let along = s2.add(T::TWO.mul(c).mul(cos)).mul(omega.prod_dot(v));
529 let position = (v.prod_scalar(two_s_cos))
530 .add(&omega.prod_scalar(along))
531 .add(&omega.prod_cross(v).prod_scalar(T::TWO.mul(s2)));
532 Ok(Pose { rotation, position })
533 }
534
535 pub fn apply(&self, p: &Vector3<T>) -> Vector3<T> {
538 let r = &self.rotation;
539 let turned = self.unturned(r.mul(&Quaternion::pure(p)).mul(&r.conj()));
540 turned.vector().add(&self.position)
541 }
542
543 fn unturned(&self, q: Quaternion<T>) -> Quaternion<T> {
550 let n = self.rotation.norm_sq();
551 if n.is_exactly(T::ONE) {
552 return q;
553 }
554 q.scale(
556 T::ONE
557 .div(n)
558 .expect("a pose's rotation is not the zero quaternion"),
559 )
560 }
561
562 pub fn rotation_about(
569 point: &Vector3<T>,
570 direction: &Vector3<T>,
571 radians: T,
572 ) -> GeopResult<Self> {
573 let half = radians.div(T::TWO)?;
574 let (c, s) = (half.cos(), half.sin());
575 let turn = Pose {
576 rotation: Quaternion::new(
577 c,
578 s.mul(direction[0]),
579 s.mul(direction[1]),
580 s.mul(direction[2]),
581 ),
582 position: Vector3::zero(),
583 };
584 if !turn.rotation.norm_sq().definitely_greater(T::ZERO) {
585 return Err(GeopError::new(format!(
586 "Pose::rotation_about: the direction {direction:?} is no unit vector"
587 )));
588 }
589 Ok(turn.with_position(point.sub(&turn.apply(point))))
590 }
591
592 pub fn motion(&self) -> Motion<T> {
597 let turns = !self.rotation.is_identity();
598 Motion {
599 axes: turns.then(|| {
600 self.rotation
601 .rotation_columns()
602 .expect("a pose's rotation is no zero quaternion")
603 }),
604 position: self.position,
605 mirrors: false,
606 }
607 }
608}
609
610impl Pose {
611 #[allow(clippy::needless_range_loop, reason = "a column of each of two blocks")]
621 pub fn exp_differential(omega: &Vector3, v: &Vector3) -> GeopResult<[[Scalar; 6]; 6]> {
622 let moved = Pose::exp_with_derivatives(omega, v)?;
623 let r = moved.rotation();
624 let at = r.map(|c| c.v);
625 let [x, y, z] = at.rotation_columns()?;
626 let mut jacobian = [[Scalar::ZERO; 6]; 6];
627 for k in 0..6 {
628 let dr = r.map(|c| c.d[k]);
629 let turn = at.conj().mul(&dr).vector();
630 let dt = Vector3::from_array([0, 1, 2].map(|i| moved.position()[i].d[k]));
631 let along = [x.prod_dot(&dt), y.prod_dot(&dt), z.prod_dot(&dt)];
632 for i in 0..3 {
633 jacobian[i][k] = Scalar::TWO.mul(turn[i]);
634 jacobian[3 + i][k] = along[i];
635 }
636 }
637 Ok(jacobian)
638 }
639}
640
641impl Pose<Dual<6>> {
645 pub fn exp_with_derivatives(omega: &Vector3, v: &Vector3) -> GeopResult<Self> {
654 thread_local! {
655 static AT_ZERO: std::cell::Cell<Option<Pose<Dual<6>>>> =
656 const { std::cell::Cell::new(None) };
657 }
658 let zero = |x: &Vector3| x.to_array().iter().all(|c| c.is_zero() && c.is_sharp());
659 let at_zero = zero(omega) && zero(v);
660 if at_zero && let Some(made) = AT_ZERO.get() {
661 return Ok(made);
662 }
663 let var = |k: usize, x: Scalar| Dual::var(x, k);
664 let made = Pose::exp(
665 &Vector3::from_array([var(0, omega[0]), var(1, omega[1]), var(2, omega[2])]),
666 &Vector3::from_array([var(3, v[0]), var(4, v[1]), var(5, v[2])]),
667 )?;
668 if at_zero {
669 AT_ZERO.set(Some(made));
670 }
671 Ok(made)
672 }
673}
674
675impl Variable for Pose {
676 fn dimension(&self) -> usize {
677 6
678 }
679
680 fn retract(&self, step: &[Scalar]) -> GeopResult<Self> {
681 let (omega, v) = twist(step)?;
682 Ok(self.compose(&Pose::exp(&omega, &v)?).map(Scalar::sharpen))
683 }
684
685 fn retract_over(&self, step: &[Scalar]) -> GeopResult<Self> {
691 let unit = Pose {
692 rotation: self.rotation.normalized()?,
693 position: self.position,
694 };
695 if step.iter().all(|d| d.is_zero() && d.is_sharp()) {
696 return Ok(unit);
697 }
698 let (omega, v) = twist(step)?;
699 Ok(unit.compose(&Pose::exp(&omega, &v)?))
700 }
701
702 fn differential(&self, step: &[Scalar]) -> GeopResult<Option<Vec<Vec<Scalar>>>> {
705 if step.iter().all(|d| d.is_zero() && d.is_sharp()) {
706 return Ok(None);
707 }
708 let (omega, v) = twist(step)?;
709 let jacobian = Pose::exp_differential(&omega, &v)?;
710 Ok(Some(jacobian.iter().map(|row| row.to_vec()).collect()))
711 }
712
713 fn stay(&self, at: &Self, scale: Scalar) -> GeopResult<Rows> {
718 type D = crate::dual::Dual<6>;
719 let step = Pose::exp_with_derivatives(&Vector3::zero(), &Vector3::zero())?;
720 let moved = self.map(D::cst).compose(&step);
721 let offset = moved.position().sub(&at.position().map(D::cst));
722 let r = moved.rotation();
723 let mut target = at.rotation();
724 if r.map(|c| c.v).dot(&target).definitely_less(Scalar::ZERO) {
725 target = target.scale(Scalar::ONE.neg());
726 }
727 let turned = (r.components().into_iter().zip(target.components()))
728 .map(|(c, t)| c.sub(D::cst(t)).mul(D::cst(scale)));
729 let mut rows = Rows::default();
730 for residual in offset.to_array().into_iter().chain(turned) {
731 rows.push(residual.v, residual.d.to_vec());
732 }
733 Ok(rows)
734 }
735
736 fn same(&self, other: &Self) -> bool {
737 self == other
738 }
739
740 fn carried(&self, from: &Self, to: &Self) -> Option<Self> {
742 Some(
743 to.compose(&from.inverse())
744 .compose(self)
745 .map(Scalar::sharpen),
746 )
747 }
748}
749
750fn twist(step: &[Scalar]) -> GeopResult<(Vector3, Vector3)> {
752 match step {
753 [w0, w1, w2, v0, v1, v2] => Ok((
754 Vector3::from_array([*w0, *w1, *w2]),
755 Vector3::from_array([*v0, *v1, *v2]),
756 )),
757 _ => Err(GeopError::new(format!(
758 "a step of a pose is six numbers, not {}",
759 step.len()
760 ))),
761 }
762}
763
764#[derive(Clone, Copy, Debug, PartialEq)]
775pub struct Motion<T: Number = Scalar> {
776 axes: Option<[Vector3<T>; 3]>,
777 position: Vector3<T>,
778 mirrors: bool,
779}
780
781impl<T: Number> Motion<T> {
782 pub fn translation(offset: Vector3<T>) -> Self {
784 Motion {
785 axes: None,
786 position: offset,
787 mirrors: false,
788 }
789 }
790
791 pub fn mirror(point: &Vector3<T>, normal: &Vector3<T>) -> GeopResult<Self> {
796 let n = normal.normalize()?;
797 let column = |k: usize| {
798 let mut c = n.prod_scalar(T::TWO.mul(n[k]).neg());
799 c[k] = T::ONE.add(c[k]);
800 c
801 };
802 Ok(Motion {
803 axes: Some([column(0), column(1), column(2)]),
804 position: n.prod_scalar(T::TWO.mul(point.prod_dot(&n))),
805 mirrors: true,
806 })
807 }
808
809 pub fn mirrors(&self) -> bool {
812 self.mirrors
813 }
814
815 pub fn turns(&self) -> bool {
818 self.axes.is_some()
819 }
820
821 pub fn position(&self) -> Vector3<T> {
824 self.position
825 }
826
827 pub fn rotate(&self, d: &Vector3<T>) -> Vector3<T> {
830 match &self.axes {
831 None => *d,
832 Some([x, y, z]) => x
833 .prod_scalar(d[0])
834 .add(&y.prod_scalar(d[1]))
835 .add(&z.prod_scalar(d[2])),
836 }
837 }
838
839 pub fn apply(&self, p: &Vector3<T>) -> Vector3<T> {
841 self.rotate(p).add(&self.position)
842 }
843}
844
845impl Motion {
846 pub fn affine(columns: [Vector3; 3], position: Vector3) -> GeopResult<Self> {
855 let det = columns[0].prod_dot(&columns[1].prod_cross(&columns[2]));
856 if det.could_be_equal(Scalar::ZERO) {
857 return Err(GeopError::new(format!(
858 "Motion::affine: the columns {columns:?} flatten everything, their determinant being {det:?}"
859 )));
860 }
861 Ok(Motion {
862 axes: Some(columns),
863 position,
864 mirrors: det.definitely_less(Scalar::ZERO),
865 })
866 }
867
868 pub fn scaling(center: &Vector3, factors: [Scalar; 3]) -> GeopResult<Self> {
873 if let Some(zero) = factors.iter().find(|f| f.could_be_equal(Scalar::ZERO)) {
874 return Err(GeopError::new(format!(
875 "Motion::scaling: a factor of {zero:?} flattens everything"
876 )));
877 }
878 let column = |k: usize| {
879 let mut c = Vector3::zero();
880 c[k] = factors[k];
881 c
882 };
883 let position = Vector3::from_array(std::array::from_fn(|k| {
884 center[k].sub(factors[k].mul(center[k]))
885 }));
886 Self::affine([column(0), column(1), column(2)], position)
887 }
888
889 pub fn apply_frame(&self, frame: &CoordinateSystem) -> GeopResult<CoordinateSystem> {
892 if self.mirrors {
893 return Err(GeopError::new(
894 "Motion::apply_frame: a mirrored frame is left-handed",
895 ));
896 }
897 CoordinateSystem::try_new(
898 self.apply(frame.origin()),
899 self.rotate(frame.u()),
900 self.rotate(frame.v()),
901 self.rotate(frame.w()),
902 )
903 }
904}
905
906#[cfg(test)]
907mod tests {
908 use super::*;
909
910 fn v(p: [f64; 3]) -> Vector3 {
911 Vector3::from_array(p.map(Scalar::from_f64))
912 }
913
914 fn pose(position: [f64; 3], degrees: [f64; 3]) -> Pose {
915 Pose::from_euler(v(position), degrees.map(Scalar::from_f64)).unwrap()
916 }
917
918 fn encloses(a: &Vector3, b: [f64; 3]) -> bool {
920 (0..3).all(|k| a[k].could_be_equal(Scalar::from_f64(b[k])))
921 }
922
923 fn close(a: &Vector3, b: &Vector3) -> bool {
924 (0..3).all(|k| (a[k].to_f64() - b[k].to_f64()).abs() < 1e-12)
925 }
926
927 #[test]
931 fn rotations_are_read_back_from_their_matrices() {
932 for degrees in [
933 [0.0, 0.0, 0.0],
934 [180.0, 0.0, 0.0],
935 [0.0, 180.0, 0.0],
936 [0.0, 0.0, 180.0],
937 [30.0, -70.0, 125.0],
938 [170.0, 10.0, -175.0],
939 ] {
940 let rotation = pose([0.0; 3], degrees).rotation();
941 let columns = rotation.rotation_columns().unwrap();
942 let back = Quaternion::from_rotation_columns(columns).unwrap();
943 let again = back.rotation_columns().unwrap();
944 for k in 0..3 {
945 assert!(close(&columns[k], &again[k]), "{degrees:?}");
946 }
947 }
948 }
949
950 #[test]
953 fn angles_turn_about_fixed_axes() {
954 let turned = pose([1.0, 2.0, 3.0], [0.0, 0.0, 90.0]);
955 assert!(encloses(
956 &turned.apply(&v([1.0, 0.0, 0.0])),
957 [1.0, 3.0, 3.0]
958 ));
959 assert!(encloses(
960 &turned.motion().apply(&v([1.0, 0.0, 0.0])),
961 [1.0, 3.0, 3.0]
962 ));
963 let both = pose([0.0; 3], [90.0, 0.0, 90.0]);
965 assert!(encloses(&both.apply(&v([1.0, 0.0, 0.0])), [0.0, 1.0, 0.0]));
966 assert!(encloses(&both.apply(&v([0.0, 1.0, 0.0])), [0.0, 0.0, 1.0]));
967 }
968
969 #[test]
971 fn angles_round_trip() {
972 for degrees in [
973 [10.0, 20.0, 30.0],
974 [-170.0, 45.0, 100.0],
975 [30.0, 90.0, 20.0],
976 ] {
977 let original = pose([0.5, -1.0, 2.0], degrees);
978 let back = Pose::from_euler(
979 original.position(),
980 original.euler_degrees().map(Scalar::from_f64),
981 )
982 .unwrap();
983 for p in [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.3, -0.2, 0.9]] {
984 assert!(
985 close(&back.apply(&v(p)), &original.apply(&v(p))),
986 "{degrees:?}"
987 );
988 }
989 }
990 }
991
992 #[test]
993 fn inverse_and_compose_undo_each_other() {
994 let a = pose([0.5, -1.0, 2.0], [10.0, -20.0, 70.0]);
995 let p = [0.3, 0.4, 0.5];
996 assert!(encloses(&a.compose(&a.inverse()).apply(&v(p)), p));
997 let b = pose([1.0, 0.0, -1.0], [45.0, 30.0, 0.0]);
998 let q = v([0.3, -0.7, 0.2]);
999 let composed = a.compose(&b).apply(&q);
1000 let each = a.apply(&b.apply(&q));
1001 assert!((0..3).all(|k| composed[k].could_be_equal(each[k])));
1002 }
1003
1004 #[test]
1007 fn poses_read_and_write_position_and_quaternion() {
1008 let written = Pose::new(
1009 v([1.0, 2.0, 3.0]),
1010 Quaternion::new(Scalar::ZERO, Scalar::ZERO, Scalar::ZERO, Scalar::TWO),
1011 )
1012 .unwrap();
1013 let json = serde_json::to_value(written).unwrap();
1014 assert_eq!(
1015 json,
1016 serde_json::json!({"position": [1.0, 2.0, 3.0], "rotation": [0.0, 0.0, 0.0, 1.0]})
1017 );
1018 let read: Pose = serde_json::from_value(json).unwrap();
1019 assert!(encloses(&read.apply(&v([1.0, 0.0, 0.0])), [0.0, 2.0, 3.0]));
1020 let zero =
1021 serde_json::json!({"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 0.0]});
1022 assert!(serde_json::from_value::<Pose>(zero).is_err());
1023 }
1024
1025 #[test]
1028 fn a_pose_read_is_written_back_as_it_was() {
1029 let file = serde_json::json!({
1030 "position": [34.18148346551969, 62.89507987346716, 5.871850811598488e-15],
1031 "rotation": [0.9731912888550477, -5.175907578430137e-17, 4.296936517018427e-17,
1032 -0.22999720714967709],
1033 });
1034 let read: Pose = serde_json::from_value(file.clone()).unwrap();
1035 let written = serde_json::to_value(read).unwrap();
1036 assert_eq!(written, file);
1037 assert_eq!(serde_json::from_value::<Pose>(written).unwrap(), read);
1038 let turned = pose([1.0, -2.0, 0.5], [10.0, 20.0, 30.0]);
1039 let saved = turned.saved().unwrap();
1040 assert_eq!(saved.saved().unwrap(), saved);
1041 }
1042
1043 #[test]
1047 fn a_rotation_read_off_unit_turns_as_its_unit_one() {
1048 let turned = pose([0.0; 3], [10.0, 20.0, 30.0]);
1049 let saved = turned.saved().unwrap();
1050 let unit = Pose::new(saved.position(), saved.rotation()).unwrap();
1051 let p = v([3.0, -1.0, 2.0]);
1052 let (a, b) = (saved.apply(&p), unit.apply(&p));
1053 assert!((0..3).all(|k| a[k].could_be_equal(b[k])), "{a:?} {b:?}");
1054 }
1055
1056 #[test]
1059 fn rotation_about_a_line_keeps_it() {
1060 let turn = Pose::rotation_about(
1061 &v([1.0, 1.0, 0.0]),
1062 &v([0.0, 0.0, 1.0]),
1063 Scalar::PI.div(Scalar::TWO).unwrap(),
1064 )
1065 .unwrap();
1066 assert!(encloses(&turn.apply(&v([1.0, 1.0, 7.0])), [1.0, 1.0, 7.0]));
1067 assert!(encloses(&turn.apply(&v([2.0, 1.0, 5.0])), [1.0, 2.0, 5.0]));
1068 let motion = turn.motion();
1069 assert!(motion.turns() && !motion.mirrors());
1070 assert!(encloses(
1071 &motion.apply(&v([2.0, 1.0, 5.0])),
1072 [1.0, 2.0, 5.0]
1073 ));
1074 }
1075
1076 #[test]
1080 fn translations_and_mirrors() {
1081 let shift = Pose::identity().with_position(v([0.1, 0.2, 0.3])).motion();
1082 assert!(!shift.turns());
1083 let p = v([1.0, 2.0, 3.0]);
1084 assert_eq!(
1085 format!("{:?}", shift.apply(&p)),
1086 format!("{:?}", p.add(&v([0.1, 0.2, 0.3])))
1087 );
1088 let mirror = Motion::mirror(&v([1.0, 0.0, 2.0]), &v([1.0, -2.0, 0.5])).unwrap();
1089 assert!(mirror.mirrors());
1090 let q = v([0.3, -0.7, 4.0]);
1091 assert!(mirror.apply(&mirror.apply(&q)).could_be_equal(&q));
1092 assert!(encloses(
1093 &mirror.apply(&v([1.0, 0.0, 2.0])),
1094 [1.0, 0.0, 2.0]
1095 ));
1096 let (x, y, z) = (v([1.0, 0.0, 0.0]), v([0.0, 1.0, 0.0]), v([0.0, 0.0, 1.0]));
1097 let handedness = mirror
1098 .rotate(&x)
1099 .prod_cross(&mirror.rotate(&y))
1100 .prod_dot(&mirror.rotate(&z));
1101 assert!(
1102 handedness.could_be_equal(Scalar::ONE.neg()),
1103 "{handedness:?}"
1104 );
1105 assert!(mirror.apply_frame(&CoordinateSystem::world_at(q)).is_err());
1106 }
1107
1108 #[test]
1112 fn exp_is_the_motion_of_a_twist() {
1113 let zero = Pose::exp(&v([0.0; 3]), &v([0.0; 3])).unwrap();
1114 assert!(encloses(&zero.apply(&v([1.0, 2.0, 3.0])), [1.0, 2.0, 3.0]));
1115
1116 let quarter = std::f64::consts::FRAC_PI_2;
1117 let turn = Pose::exp(&v([0.0, 0.0, quarter]), &v([0.0; 3])).unwrap();
1118 let p = turn.apply(&v([1.0, 0.0, 0.0]));
1119 assert!(
1120 (p[0].to_f64()).abs() < 1e-15 && (p[1].to_f64() - 1.0).abs() < 1e-15,
1121 "{p:?}"
1122 );
1123
1124 let shift = Pose::exp(&v([0.0; 3]), &v([1.0, 2.0, 3.0])).unwrap();
1125 assert!(encloses(&shift.position(), [1.0, 2.0, 3.0]));
1126
1127 for angle in [0.3, 1.9, 3.0, 5.5] {
1128 let (axis, q) = (v([0.0, 0.0, 1.0]), v([2.0, 1.0, 0.5]));
1129 let omega = axis.prod_scalar(Scalar::from_f64(angle));
1130 let screw = Pose::exp(&omega, &omega.prod_cross(&q).neg()).unwrap();
1131 let about = Pose::rotation_about(&q, &axis, Scalar::from_f64(angle)).unwrap();
1132 for point in [[0.0; 3], [1.0, -2.0, 4.0]] {
1133 let (a, b) = (screw.apply(&v(point)), about.apply(&v(point)));
1134 for k in 0..3 {
1135 let gap = (a[k].to_f64() - b[k].to_f64()).abs();
1136 assert!(gap < 1e-12, "{angle}: {a:?} vs {b:?}");
1137 }
1138 }
1139 }
1140 }
1141
1142 #[test]
1145 fn exp_is_continuous_where_its_series_ends() {
1146 let at = |angle: f64| {
1147 let omega = v([0.6, 0.0, 0.8]).prod_scalar(Scalar::from_f64(angle));
1148 Pose::exp(&omega, &v([0.3, -1.0, 2.0])).unwrap()
1149 };
1150 let (below, above) = (at(2.0 - 1e-12), at(2.0 + 1e-12));
1151 for point in [[0.0; 3], [1.0, 1.0, 1.0]] {
1152 let (a, b) = (below.apply(&v(point)), above.apply(&v(point)));
1153 for k in 0..3 {
1154 assert!(
1155 (a[k].to_f64() - b[k].to_f64()).abs() < 1e-10,
1156 "{a:?} vs {b:?}"
1157 );
1158 }
1159 }
1160 }
1161
1162 #[test]
1165 fn exp_differentiates_at_zero() {
1166 use crate::dual::Dual;
1167 type D = Dual<6>;
1168 let var = |k: usize| D::var(Scalar::ZERO, k);
1169 let omega = Vector3::from_array([var(0), var(1), var(2)]);
1170 let velocity = Vector3::from_array([var(3), var(4), var(5)]);
1171 let moved = Pose::<D>::exp(&omega, &velocity).unwrap();
1172 let p = [1.0, 2.0, 3.0];
1173 let at = moved.apply(&Vector3::from_array(p.map(|c| D::cst(Scalar::from_f64(c)))));
1174 let expected = [
1176 [0.0, p[2], -p[1], 1.0, 0.0, 0.0],
1177 [-p[2], 0.0, p[0], 0.0, 1.0, 0.0],
1178 [p[1], -p[0], 0.0, 0.0, 0.0, 1.0],
1179 ];
1180 for (k, row) in expected.iter().enumerate() {
1181 assert!(at[k].v.could_be_equal(Scalar::from_f64(p[k])));
1182 for (j, &exact) in row.iter().enumerate() {
1183 let d = at[k].d[j];
1184 assert!(
1185 d.could_be_equal(Scalar::from_f64(exact)),
1186 "∂{k}/∂{j} = {d:?}"
1187 );
1188 }
1189 }
1190 }
1191
1192 #[test]
1196 fn a_rotation_off_unit_moves_points_as_its_unit_one() {
1197 let saved: Pose = serde_json::from_value(serde_json::json!({
1198 "position": [1.0, -7.25, 2.0],
1199 "rotation": [1.09, 0.2, -0.3, 0.1],
1200 }))
1201 .unwrap();
1202 let unit = Pose::new(saved.position(), saved.rotation()).unwrap();
1203 let p = [3.0, 4.0, -5.0];
1204 let moved = unit.apply(&v(p)).to_f64_array();
1205 assert!(
1206 encloses(&saved.apply(&v(p)), moved),
1207 "{:?}",
1208 saved.apply(&v(p))
1209 );
1210 let back = saved.inverse().compose(&saved);
1211 assert!(encloses(&back.apply(&v(p)), p), "{back:?}");
1212 assert!(back.rotation().norm_sq().could_be_equal(Scalar::ONE));
1213 assert!(encloses(&saved.inverse().apply(&saved.apply(&v(p))), p));
1214 }
1215}