Skip to main content

cadcore_math/
transform.rs

1//! Rigid-body transformation (`Transform3`): rotation + translation.
2//!
3//! Represents the map  `p ↦ R·p + t`  where `R` is an orthogonal 3×3 matrix
4//! and `t` is a translation vector.
5
6use std::ops::Mul;
7use crate::{Mat3, Point3, Vec3};
8
9/// A rigid (isometric) transformation in 3-D space.
10///
11/// Composes as: `(B ∘ A).apply(p) == B.apply(A.apply(p))`.
12#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct Transform3 {
14    /// Linear (rotation / reflection) part.
15    pub rotation:    Mat3,
16    /// Translation part (applied *after* the rotation).
17    pub translation: Vec3,
18}
19
20impl Transform3 {
21    /// Identity transform.
22    pub const IDENTITY: Self = Self {
23        rotation:    Mat3::IDENTITY,
24        translation: Vec3::ZERO,
25    };
26
27    /// Pure translation.
28    #[inline]
29    pub fn from_translation(t: Vec3) -> Self {
30        Self { rotation: Mat3::IDENTITY, translation: t }
31    }
32
33    /// Pure rotation around `axis` by `angle` radians.
34    #[inline]
35    pub fn from_rotation(axis: crate::UnitVec3, angle: f64) -> Self {
36        Self { rotation: Mat3::rotation(axis, angle), translation: Vec3::ZERO }
37    }
38
39    /// Apply the transform to a point.
40    #[inline]
41    pub fn apply_point(self, p: Point3) -> Point3 {
42        Point3::from_vec(self.rotation * p.to_vec()) + self.translation
43    }
44
45    /// Apply only the rotation part to a vector (no translation).
46    #[inline]
47    pub fn apply_vec(self, v: Vec3) -> Vec3 {
48        self.rotation * v
49    }
50
51    /// Inverse transform.
52    pub fn inverse(self) -> Self {
53        let r_inv = self.rotation.transpose(); // orthogonal ⟹ R⁻¹ = Rᵀ
54        let t_inv = -(r_inv * self.translation);
55        Self { rotation: r_inv, translation: t_inv }
56    }
57
58    /// Compose two transforms: `self ∘ rhs` (apply `rhs` first).
59    #[inline]
60    pub fn compose(self, rhs: Self) -> Self {
61        Self {
62            rotation:    self.rotation * rhs.rotation,
63            translation: self.rotation * rhs.translation + self.translation,
64        }
65    }
66}
67
68/// Composition operator: `lhs * rhs` applies `rhs` first.
69impl Mul for Transform3 {
70    type Output = Self;
71    #[inline] fn mul(self, rhs: Self) -> Self { self.compose(rhs) }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use crate::UnitVec3;
78
79    #[test]
80    fn inverse_round_trip() {
81        let t = Transform3 {
82            rotation:    Mat3::rotation(UnitVec3::Z, 0.7),
83            translation: Vec3::new(1.0, 2.0, 3.0),
84        };
85        let p = Point3::new(4.0, 5.0, 6.0);
86        let q = t.inverse().apply_point(t.apply_point(p));
87        assert!((p - q).length() < 1e-10);
88    }
89}