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 crate::{Mat3, Point3, Vec3};
7use std::ops::Mul;
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 {
31            rotation: Mat3::IDENTITY,
32            translation: t,
33        }
34    }
35
36    /// Pure rotation around `axis` by `angle` radians.
37    #[inline]
38    pub fn from_rotation(axis: crate::UnitVec3, angle: f64) -> Self {
39        Self {
40            rotation: Mat3::rotation(axis, angle),
41            translation: Vec3::ZERO,
42        }
43    }
44
45    /// Apply the transform to a point.
46    #[inline]
47    pub fn apply_point(self, p: Point3) -> Point3 {
48        Point3::from_vec(self.rotation * p.to_vec()) + self.translation
49    }
50
51    /// Apply only the rotation part to a vector (no translation).
52    #[inline]
53    pub fn apply_vec(self, v: Vec3) -> Vec3 {
54        self.rotation * v
55    }
56
57    /// Inverse transform.
58    pub fn inverse(self) -> Self {
59        let r_inv = self.rotation.transpose(); // orthogonal ⟹ R⁻¹ = Rᵀ
60        let t_inv = -(r_inv * self.translation);
61        Self {
62            rotation: r_inv,
63            translation: t_inv,
64        }
65    }
66
67    /// Compose two transforms: `self ∘ rhs` (apply `rhs` first).
68    #[inline]
69    pub fn compose(self, rhs: Self) -> Self {
70        Self {
71            rotation: self.rotation * rhs.rotation,
72            translation: self.rotation * rhs.translation + self.translation,
73        }
74    }
75}
76
77/// Composition operator: `lhs * rhs` applies `rhs` first.
78impl Mul for Transform3 {
79    type Output = Self;
80    #[inline]
81    fn mul(self, rhs: Self) -> Self {
82        self.compose(rhs)
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use crate::UnitVec3;
90
91    #[test]
92    fn inverse_round_trip() {
93        let t = Transform3 {
94            rotation: Mat3::rotation(UnitVec3::Z, 0.7),
95            translation: Vec3::new(1.0, 2.0, 3.0),
96        };
97        let p = Point3::new(4.0, 5.0, 6.0);
98        let q = t.inverse().apply_point(t.apply_point(p));
99        assert!((p - q).length() < 1e-10);
100    }
101}