1use nalgebra::{Matrix3, Matrix3x4, UnitQuaternion, Vector2, Vector3};
31use std::ops::Mul;
32
33pub type Vec2 = Vector2<f64>;
35pub type Vec3 = Vector3<f64>;
37pub type Mat3 = Matrix3<f64>;
39pub type Mat3x4 = Matrix3x4<f64>;
41
42#[derive(Clone, Copy, Debug, PartialEq)]
44pub struct Quat {
45 pub w: f64,
47 pub x: f64,
49 pub y: f64,
51 pub z: f64,
53}
54
55impl Default for Quat {
56 fn default() -> Self {
57 Self::IDENTITY
58 }
59}
60
61impl Quat {
62 pub const IDENTITY: Quat = Quat { w: 1.0, x: 0.0, y: 0.0, z: 0.0 };
64
65 pub fn new(w: f64, x: f64, y: f64, z: f64) -> Self {
67 Self { w, x, y, z }
68 }
69 pub fn from_wxyz(v: [f64; 4]) -> Self {
71 Self { w: v[0], x: v[1], y: v[2], z: v[3] }
72 }
73 pub fn to_wxyz(&self) -> [f64; 4] {
75 [self.w, self.x, self.y, self.z]
76 }
77 pub fn norm(&self) -> f64 {
79 (self.w * self.w + self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
80 }
81 pub fn normalized(&self) -> Self {
83 let n = self.norm();
84 if n == 0.0 || !n.is_finite() {
85 return Self::IDENTITY;
86 }
87 Self { w: self.w / n, x: self.x / n, y: self.y / n, z: self.z / n }
88 }
89 pub fn conjugate(&self) -> Self {
91 Self { w: self.w, x: -self.x, y: -self.y, z: -self.z }
92 }
93 pub fn inverse(&self) -> Self {
95 self.conjugate()
96 }
97 pub fn dot(&self, o: &Quat) -> f64 {
99 self.w * o.w + self.x * o.x + self.y * o.y + self.z * o.z
100 }
101 pub fn hamilton(&self, o: &Quat) -> Quat {
103 Quat {
104 w: self.w * o.w - self.x * o.x - self.y * o.y - self.z * o.z,
105 x: self.w * o.x + self.x * o.w + self.y * o.z - self.z * o.y,
106 y: self.w * o.y - self.x * o.z + self.y * o.w + self.z * o.x,
107 z: self.w * o.z + self.x * o.y - self.y * o.x + self.z * o.w,
108 }
109 }
110 pub fn to_rotation_matrix(&self) -> Mat3 {
112 let n2 = self.w * self.w + self.x * self.x + self.y * self.y + self.z * self.z;
113 let s = if n2 > 0.0 { 2.0 / n2 } else { 0.0 };
114 let (w, x, y, z) = (self.w, self.x, self.y, self.z);
115 let (xx, yy, zz) = (x * x * s, y * y * s, z * z * s);
116 let (xy, xz, yz) = (x * y * s, x * z * s, y * z * s);
117 let (wx, wy, wz) = (w * x * s, w * y * s, w * z * s);
118 Mat3::new(1.0 - yy - zz, xy - wz, xz + wy, xy + wz, 1.0 - xx - zz, yz - wx, xz - wy, yz + wx, 1.0 - xx - yy)
119 }
120 pub fn from_rotation_matrix(m: &Mat3) -> Quat {
122 let tr = m[(0, 0)] + m[(1, 1)] + m[(2, 2)];
123 let q = if tr > 0.0 {
124 let s = (tr + 1.0).sqrt() * 2.0;
125 Quat { w: 0.25 * s, x: (m[(2, 1)] - m[(1, 2)]) / s, y: (m[(0, 2)] - m[(2, 0)]) / s, z: (m[(1, 0)] - m[(0, 1)]) / s }
126 } else if m[(0, 0)] > m[(1, 1)] && m[(0, 0)] > m[(2, 2)] {
127 let s = (1.0 + m[(0, 0)] - m[(1, 1)] - m[(2, 2)]).sqrt() * 2.0;
128 Quat { w: (m[(2, 1)] - m[(1, 2)]) / s, x: 0.25 * s, y: (m[(0, 1)] + m[(1, 0)]) / s, z: (m[(0, 2)] + m[(2, 0)]) / s }
129 } else if m[(1, 1)] > m[(2, 2)] {
130 let s = (1.0 + m[(1, 1)] - m[(0, 0)] - m[(2, 2)]).sqrt() * 2.0;
131 Quat { w: (m[(0, 2)] - m[(2, 0)]) / s, x: (m[(0, 1)] + m[(1, 0)]) / s, y: 0.25 * s, z: (m[(1, 2)] + m[(2, 1)]) / s }
132 } else {
133 let s = (1.0 + m[(2, 2)] - m[(0, 0)] - m[(1, 1)]).sqrt() * 2.0;
134 Quat { w: (m[(1, 0)] - m[(0, 1)]) / s, x: (m[(0, 2)] + m[(2, 0)]) / s, y: (m[(1, 2)] + m[(2, 1)]) / s, z: 0.25 * s }
135 };
136 q.normalized()
137 }
138 pub fn from_axis_angle(axis: &Vec3, angle: f64) -> Quat {
140 let n = axis.norm();
141 if n == 0.0 {
142 return Self::IDENTITY;
143 }
144 let a = axis / n;
145 let (s, c) = (0.5 * angle).sin_cos();
146 Quat { w: c, x: a.x * s, y: a.y * s, z: a.z * s }
147 }
148 pub fn from_rotation_vector(rv: &Vec3) -> Quat {
150 let theta = rv.norm();
151 if theta < 1e-12 {
152 return Quat { w: 1.0, x: 0.5 * rv.x, y: 0.5 * rv.y, z: 0.5 * rv.z }.normalized();
154 }
155 Self::from_axis_angle(rv, theta)
156 }
157 pub fn to_rotation_vector(&self) -> Vec3 {
159 let q = self.normalized();
160 let q = if q.w < 0.0 { Quat { w: -q.w, x: -q.x, y: -q.y, z: -q.z } } else { q };
161 let v = Vec3::new(q.x, q.y, q.z);
162 let s = v.norm();
163 if s < 1e-12 {
164 return 2.0 * v;
165 }
166 let angle = 2.0 * s.atan2(q.w);
167 v * (angle / s)
168 }
169 pub fn rotate(&self, v: &Vec3) -> Vec3 {
171 self.to_rotation_matrix() * v
172 }
173 pub fn angular_distance(&self, other: &Quat) -> f64 {
175 let d = self.normalized().dot(&other.normalized()).abs().min(1.0);
176 2.0 * d.acos()
177 }
178 pub fn to_nalgebra(&self) -> UnitQuaternion<f64> {
180 UnitQuaternion::from_quaternion(nalgebra::Quaternion::new(self.w, self.x, self.y, self.z))
181 }
182 pub fn from_nalgebra(q: &UnitQuaternion<f64>) -> Quat {
184 Quat { w: q.w, x: q.i, y: q.j, z: q.k }
185 }
186}
187
188impl Mul for Quat {
189 type Output = Quat;
190 fn mul(self, rhs: Quat) -> Quat {
191 self.hamilton(&rhs)
192 }
193}
194
195pub fn skew(v: &Vec3) -> Mat3 {
197 Mat3::new(0.0, -v.z, v.y, v.z, 0.0, -v.x, -v.y, v.x, 0.0)
198}
199
200#[derive(Clone, Copy, Debug, PartialEq, Default)]
202pub struct Rigid3 {
203 pub rotation: Quat,
205 pub translation: Vec3,
207}
208
209impl Rigid3 {
210 pub fn identity() -> Self {
212 Self { rotation: Quat::IDENTITY, translation: Vec3::zeros() }
213 }
214 pub fn new(rotation: Quat, translation: Vec3) -> Self {
216 Self { rotation, translation }
217 }
218 pub fn from_rotation_matrix(r: &Mat3, t: Vec3) -> Self {
220 Self { rotation: Quat::from_rotation_matrix(r), translation: t }
221 }
222 pub fn from_params(p: &[f64; 7]) -> Self {
224 Self { rotation: Quat::new(p[0], p[1], p[2], p[3]), translation: Vec3::new(p[4], p[5], p[6]) }
225 }
226 pub fn to_params(&self) -> [f64; 7] {
228 let q = self.rotation;
229 let t = self.translation;
230 [q.w, q.x, q.y, q.z, t.x, t.y, t.z]
231 }
232 pub fn rotation_matrix(&self) -> Mat3 {
234 self.rotation.to_rotation_matrix()
235 }
236 pub fn matrix(&self) -> Mat3x4 {
238 let r = self.rotation_matrix();
239 let mut m = Mat3x4::zeros();
240 m.fixed_view_mut::<3, 3>(0, 0).copy_from(&r);
241 m.set_column(3, &self.translation);
242 m
243 }
244 pub fn from_matrix(m: &Mat3x4) -> Self {
246 let r: Mat3 = m.fixed_view::<3, 3>(0, 0).into_owned();
247 Self::from_rotation_matrix(&r, m.column(3).into_owned())
248 }
249 pub fn transform_point(&self, p: &Vec3) -> Vec3 {
251 self.rotation_matrix() * p + self.translation
252 }
253 pub fn inverse(&self) -> Self {
255 let rinv = self.rotation.conjugate();
256 let rt = rinv.to_rotation_matrix();
257 Self { rotation: rinv, translation: -(rt * self.translation) }
258 }
259 pub fn compose(&self, rhs: &Rigid3) -> Rigid3 {
261 Rigid3 {
262 rotation: self.rotation.hamilton(&rhs.rotation).normalized(),
263 translation: self.translation + self.rotation_matrix() * rhs.translation,
264 }
265 }
266 pub fn center(&self) -> Vec3 {
268 -(self.rotation_matrix().transpose() * self.translation)
269 }
270 pub fn viewing_direction(&self) -> Vec3 {
272 let r = self.rotation_matrix();
273 Vec3::new(r[(2, 0)], r[(2, 1)], r[(2, 2)])
274 }
275}
276
277impl Mul for Rigid3 {
278 type Output = Rigid3;
279 fn mul(self, rhs: Rigid3) -> Rigid3 {
280 self.compose(&rhs)
281 }
282}
283
284impl Mul<Vec3> for Rigid3 {
285 type Output = Vec3;
286 fn mul(self, rhs: Vec3) -> Vec3 {
287 self.transform_point(&rhs)
288 }
289}
290
291#[derive(Clone, Copy, Debug, PartialEq)]
293pub struct Sim3 {
294 pub scale: f64,
296 pub rotation: Quat,
298 pub translation: Vec3,
300}
301
302impl Default for Sim3 {
303 fn default() -> Self {
304 Self::identity()
305 }
306}
307
308impl Sim3 {
309 pub fn identity() -> Self {
311 Self { scale: 1.0, rotation: Quat::IDENTITY, translation: Vec3::zeros() }
312 }
313 pub fn new(scale: f64, rotation: Quat, translation: Vec3) -> Self {
315 Self { scale, rotation, translation }
316 }
317 pub fn matrix(&self) -> Mat3x4 {
319 let r = self.rotation.to_rotation_matrix() * self.scale;
320 let mut m = Mat3x4::zeros();
321 m.fixed_view_mut::<3, 3>(0, 0).copy_from(&r);
322 m.set_column(3, &self.translation);
323 m
324 }
325 pub fn from_matrix(m: &Mat3x4) -> Self {
327 let sr: Mat3 = m.fixed_view::<3, 3>(0, 0).into_owned();
328 let s = sr.column(0).norm();
329 Self { scale: s, rotation: Quat::from_rotation_matrix(&(sr / s)), translation: m.column(3).into_owned() }
330 }
331 pub fn transform_point(&self, p: &Vec3) -> Vec3 {
333 self.scale * (self.rotation.to_rotation_matrix() * p) + self.translation
334 }
335 pub fn inverse(&self) -> Self {
337 let rinv = self.rotation.conjugate();
338 Self { scale: 1.0 / self.scale, rotation: rinv, translation: -(rinv.to_rotation_matrix() * self.translation) / self.scale }
339 }
340 pub fn compose(&self, rhs: &Sim3) -> Sim3 {
342 Sim3 {
343 scale: self.scale * rhs.scale,
344 rotation: self.rotation.hamilton(&rhs.rotation).normalized(),
345 translation: self.scale * (self.rotation.to_rotation_matrix() * rhs.translation) + self.translation,
346 }
347 }
348 pub fn transform_pose(&self, world_to_cam: &Rigid3) -> Rigid3 {
350 let rot = world_to_cam.rotation.hamilton(&self.rotation.conjugate()).normalized();
351 let t = self.scale * world_to_cam.translation - rot.to_rotation_matrix() * self.translation;
352 Rigid3 { rotation: rot, translation: t }
353 }
354}
355
356impl Mul for Sim3 {
357 type Output = Sim3;
358 fn mul(self, rhs: Sim3) -> Sim3 {
359 self.compose(&rhs)
360 }
361}
362
363pub fn deg_to_rad(d: f64) -> f64 {
365 d.to_radians()
366}
367pub fn rad_to_deg(r: f64) -> f64 {
369 r.to_degrees()
370}
371
372pub fn angle_between(a: &Vec3, b: &Vec3) -> f64 {
374 let n = a.norm() * b.norm();
375 if n == 0.0 {
376 return 0.0;
377 }
378 (a.dot(b) / n).clamp(-1.0, 1.0).acos()
379}
380
381pub fn triangulation_angle(center1: &Vec3, center2: &Vec3, point: &Vec3) -> f64 {
383 let theta = angle_between(&(point - center1), &(point - center2));
384 theta.min(std::f64::consts::PI - theta)
385}
386
387pub fn baseline(center1: &Vec3, center2: &Vec3) -> f64 {
389 (center1 - center2).norm()
390}
391
392pub fn max_triangulation_angle(centers: &[Vec3], point: &Vec3) -> f64 {
394 let mut best = 0.0f64;
395 for i in 0..centers.len() {
396 for j in 0..i {
397 best = best.max(triangulation_angle(¢ers[i], ¢ers[j], point));
398 }
399 }
400 best
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 fn rand_quat(seed: u64) -> Quat {
408 let f = |k: u64| (((seed * 7919 + k * 104729) % 1000) as f64 / 500.0) - 1.0;
409 Quat::new(f(1), f(2), f(3), f(4) + 0.1).normalized()
410 }
411
412 #[test]
413 fn quat_matrix_roundtrip() {
414 for s in 0..50 {
415 let q = rand_quat(s);
416 let m = q.to_rotation_matrix();
417 assert!((m * m.transpose() - Mat3::identity()).norm() < 1e-12);
418 assert!((m.determinant() - 1.0).abs() < 1e-12);
419 let q2 = Quat::from_rotation_matrix(&m);
420 assert!(q.angular_distance(&q2) < 1e-7);
421 assert!((q2.to_rotation_matrix() - m).norm() < 1e-12);
422 }
423 }
424
425 #[test]
426 fn hamilton_convention() {
427 let i = Quat::new(0.0, 1.0, 0.0, 0.0);
429 let j = Quat::new(0.0, 0.0, 1.0, 0.0);
430 assert_eq!(i * j, Quat::new(0.0, 0.0, 0.0, 1.0));
431 let q = Quat::from_axis_angle(&Vec3::z(), std::f64::consts::FRAC_PI_2);
433 assert!((q.rotate(&Vec3::x()) - Vec3::y()).norm() < 1e-15);
434 let a = rand_quat(3);
436 let b = rand_quat(4);
437 let m = (a * b).to_rotation_matrix();
438 assert!((m - a.to_rotation_matrix() * b.to_rotation_matrix()).norm() < 1e-12);
439 let na = a.to_nalgebra();
441 assert!((na.to_rotation_matrix().matrix() - a.to_rotation_matrix()).norm() < 1e-12);
442 }
443
444 #[test]
445 fn rotation_vector_roundtrip() {
446 for s in 0..20 {
447 let q = rand_quat(s);
448 let rv = q.to_rotation_vector();
449 let q2 = Quat::from_rotation_vector(&rv);
450 assert!(q.angular_distance(&q2) < 1e-7);
451 }
452 }
453
454 #[test]
455 fn rigid3_identities() {
456 let a = Rigid3::new(rand_quat(1), Vec3::new(1.0, -2.0, 3.0));
457 let b = Rigid3::new(rand_quat(2), Vec3::new(-0.5, 0.2, 4.0));
458 let p = Vec3::new(0.3, 0.7, -1.1);
459 let ab = a * b;
460 assert!(((ab * p) - (a * (b * p))).norm() < 1e-12);
461 let ia = a.inverse();
462 assert!(((ia * (a * p)) - p).norm() < 1e-12);
463 let id = a * ia;
464 assert!(id.translation.norm() < 1e-12);
465 assert!(id.rotation.angular_distance(&Quat::IDENTITY) < 1e-7);
466 assert!((a * a.center()).norm() < 1e-12);
468 let vd = a.viewing_direction();
469 assert!((a.rotation_matrix() * vd - Vec3::z()).norm() < 1e-12);
470 let m = Rigid3::from_matrix(&a.matrix());
471 assert!(((m * p) - (a * p)).norm() < 1e-12);
472 }
473
474 #[test]
475 fn sim3_identities() {
476 let s = Sim3::new(2.5, rand_quat(5), Vec3::new(1.0, 2.0, 3.0));
477 let t = Sim3::new(0.3, rand_quat(6), Vec3::new(-1.0, 0.0, 0.5));
478 let p = Vec3::new(0.1, -0.2, 0.3);
479 assert!(((s * t).transform_point(&p) - s.transform_point(&t.transform_point(&p))).norm() < 1e-12);
480 assert!((s.inverse().transform_point(&s.transform_point(&p)) - p).norm() < 1e-12);
481 let m = Sim3::from_matrix(&s.matrix());
482 assert!((m.scale - s.scale).abs() < 1e-12);
483 assert!((m.transform_point(&p) - s.transform_point(&p)).norm() < 1e-12);
484 let pose = Rigid3::new(rand_quat(7), Vec3::new(0.2, 0.1, 5.0));
486 let np = s.transform_pose(&pose);
487 assert!((np.center() - s.transform_point(&pose.center())).norm() < 1e-10);
488 let x = Vec3::new(0.5, 0.5, 2.0);
489 let c_old = pose * x;
490 let c_new = np * s.transform_point(&x);
491 assert!((c_new - s.scale * c_old).norm() < 1e-10);
492 }
493
494 #[test]
495 fn tri_angle_symmetric() {
496 let c1 = Vec3::new(-1.0, 0.0, 0.0);
497 let c2 = Vec3::new(1.0, 0.0, 0.0);
498 let x = Vec3::new(0.0, 0.0, 1.0);
499 assert!((triangulation_angle(&c1, &c2, &x) - std::f64::consts::FRAC_PI_2).abs() < 1e-12);
500 let x = Vec3::new(0.0, 0.0, 3.0_f64.sqrt());
501 assert!((triangulation_angle(&c1, &c2, &x) - std::f64::consts::FRAC_PI_3).abs() < 1e-12);
502 let x = Vec3::new(0.0, 0.0, 1.0 / 3.0_f64.sqrt());
504 assert!((triangulation_angle(&c1, &c2, &x) - std::f64::consts::FRAC_PI_3).abs() < 1e-12);
505 assert!(triangulation_angle(&c1, &c1, &x) < 1e-7);
506 assert_eq!(triangulation_angle(&c1, &c2, &c1), 0.0);
507 }
508}