Skip to main content

use_transform/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use use_angle::Angle;
5
6/// A geometric translation.
7#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct Translation {
9    x: f64,
10    y: f64,
11    z: f64,
12}
13
14impl Translation {
15    /// Creates a translation.
16    #[must_use]
17    pub const fn new(x: f64, y: f64, z: f64) -> Self {
18        Self { x, y, z }
19    }
20
21    /// Returns the x offset.
22    #[must_use]
23    pub const fn x(self) -> f64 {
24        self.x
25    }
26
27    /// Returns the y offset.
28    #[must_use]
29    pub const fn y(self) -> f64 {
30        self.y
31    }
32
33    /// Returns the z offset.
34    #[must_use]
35    pub const fn z(self) -> f64 {
36        self.z
37    }
38}
39
40/// A geometric rotation represented by one angle.
41#[derive(Debug, Clone, Copy, PartialEq)]
42pub struct Rotation {
43    angle: Angle,
44}
45
46impl Rotation {
47    /// Creates a rotation.
48    #[must_use]
49    pub const fn new(angle: Angle) -> Self {
50        Self { angle }
51    }
52
53    /// Returns the rotation angle.
54    #[must_use]
55    pub const fn angle(self) -> Angle {
56        self.angle
57    }
58}
59
60/// A geometric scale.
61#[derive(Debug, Clone, Copy, PartialEq)]
62pub struct Scale {
63    x: f64,
64    y: f64,
65    z: f64,
66}
67
68impl Scale {
69    /// Creates a scale.
70    #[must_use]
71    pub const fn new(x: f64, y: f64, z: f64) -> Self {
72        Self { x, y, z }
73    }
74
75    /// Returns the x scale.
76    #[must_use]
77    pub const fn x(self) -> f64 {
78        self.x
79    }
80
81    /// Returns the y scale.
82    #[must_use]
83    pub const fn y(self) -> f64 {
84        self.y
85    }
86
87    /// Returns the z scale.
88    #[must_use]
89    pub const fn z(self) -> f64 {
90        self.z
91    }
92}
93
94/// A homogeneous 2D transform matrix.
95#[derive(Debug, Clone, Copy, PartialEq)]
96pub struct Transform2 {
97    matrix: [[f64; 3]; 3],
98}
99
100impl Transform2 {
101    /// Creates a transform from a 3x3 homogeneous matrix.
102    #[must_use]
103    pub const fn new(matrix: [[f64; 3]; 3]) -> Self {
104        Self { matrix }
105    }
106
107    /// Returns the identity transform.
108    #[must_use]
109    pub const fn identity() -> Self {
110        Self::new([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
111    }
112
113    /// Creates a 2D translation transform.
114    #[must_use]
115    pub const fn translation(translation: Translation) -> Self {
116        Self::new([
117            [1.0, 0.0, translation.x()],
118            [0.0, 1.0, translation.y()],
119            [0.0, 0.0, 1.0],
120        ])
121    }
122
123    /// Creates a 2D scale transform.
124    #[must_use]
125    pub const fn scale(scale: Scale) -> Self {
126        Self::new([
127            [scale.x(), 0.0, 0.0],
128            [0.0, scale.y(), 0.0],
129            [0.0, 0.0, 1.0],
130        ])
131    }
132
133    /// Creates a 2D rotation transform.
134    #[must_use]
135    pub fn rotation(rotation: Rotation) -> Self {
136        let sin = rotation.angle().radians().sin();
137        let cos = rotation.angle().radians().cos();
138        Self::new([[cos, -sin, 0.0], [sin, cos, 0.0], [0.0, 0.0, 1.0]])
139    }
140
141    /// Returns the underlying matrix.
142    #[must_use]
143    pub const fn matrix(self) -> [[f64; 3]; 3] {
144        self.matrix
145    }
146
147    /// Applies the transform to a 2D point tuple.
148    #[must_use]
149    pub fn apply_point(self, point: (f64, f64)) -> (f64, f64) {
150        let x = self.matrix[0][0].mul_add(point.0, self.matrix[0][1] * point.1) + self.matrix[0][2];
151        let y = self.matrix[1][0].mul_add(point.0, self.matrix[1][1] * point.1) + self.matrix[1][2];
152        (x, y)
153    }
154}
155
156/// A homogeneous 3D transform matrix.
157#[derive(Debug, Clone, Copy, PartialEq)]
158pub struct Transform3 {
159    matrix: [[f64; 4]; 4],
160}
161
162impl Transform3 {
163    /// Creates a transform from a 4x4 homogeneous matrix.
164    #[must_use]
165    pub const fn new(matrix: [[f64; 4]; 4]) -> Self {
166        Self { matrix }
167    }
168
169    /// Returns the identity transform.
170    #[must_use]
171    pub const fn identity() -> Self {
172        Self::new([
173            [1.0, 0.0, 0.0, 0.0],
174            [0.0, 1.0, 0.0, 0.0],
175            [0.0, 0.0, 1.0, 0.0],
176            [0.0, 0.0, 0.0, 1.0],
177        ])
178    }
179
180    /// Returns the underlying matrix.
181    #[must_use]
182    pub const fn matrix(self) -> [[f64; 4]; 4] {
183        self.matrix
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::{Rotation, Scale, Transform2, Transform3, Translation};
190    use use_angle::Angle;
191
192    #[test]
193    fn translates_points() {
194        let transform = Transform2::translation(Translation::new(2.0, 3.0, 0.0));
195
196        assert_eq!(transform.apply_point((1.0, 1.0)), (3.0, 4.0));
197    }
198
199    #[test]
200    fn scales_points() {
201        let transform = Transform2::scale(Scale::new(2.0, 3.0, 1.0));
202
203        assert_eq!(transform.apply_point((2.0, 3.0)), (4.0, 9.0));
204        assert_eq!(Transform3::identity().matrix()[3][3], 1.0);
205    }
206
207    #[test]
208    fn rotates_points() {
209        let transform = Transform2::rotation(Rotation::new(Angle::from_degrees(90.0)));
210        let rotated = transform.apply_point((1.0, 0.0));
211
212        assert!(rotated.0.abs() < 1.0e-10);
213        assert!((rotated.1 - 1.0).abs() < 1.0e-10);
214    }
215}