1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use use_angle::Angle;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct Translation {
9 x: f64,
10 y: f64,
11 z: f64,
12}
13
14impl Translation {
15 #[must_use]
17 pub const fn new(x: f64, y: f64, z: f64) -> Self {
18 Self { x, y, z }
19 }
20
21 #[must_use]
23 pub const fn x(self) -> f64 {
24 self.x
25 }
26
27 #[must_use]
29 pub const fn y(self) -> f64 {
30 self.y
31 }
32
33 #[must_use]
35 pub const fn z(self) -> f64 {
36 self.z
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq)]
42pub struct Rotation {
43 angle: Angle,
44}
45
46impl Rotation {
47 #[must_use]
49 pub const fn new(angle: Angle) -> Self {
50 Self { angle }
51 }
52
53 #[must_use]
55 pub const fn angle(self) -> Angle {
56 self.angle
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq)]
62pub struct Scale {
63 x: f64,
64 y: f64,
65 z: f64,
66}
67
68impl Scale {
69 #[must_use]
71 pub const fn new(x: f64, y: f64, z: f64) -> Self {
72 Self { x, y, z }
73 }
74
75 #[must_use]
77 pub const fn x(self) -> f64 {
78 self.x
79 }
80
81 #[must_use]
83 pub const fn y(self) -> f64 {
84 self.y
85 }
86
87 #[must_use]
89 pub const fn z(self) -> f64 {
90 self.z
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq)]
96pub struct Transform2 {
97 matrix: [[f64; 3]; 3],
98}
99
100impl Transform2 {
101 #[must_use]
103 pub const fn new(matrix: [[f64; 3]; 3]) -> Self {
104 Self { matrix }
105 }
106
107 #[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 #[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 #[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 #[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 #[must_use]
143 pub const fn matrix(self) -> [[f64; 3]; 3] {
144 self.matrix
145 }
146
147 #[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#[derive(Debug, Clone, Copy, PartialEq)]
158pub struct Transform3 {
159 matrix: [[f64; 4]; 4],
160}
161
162impl Transform3 {
163 #[must_use]
165 pub const fn new(matrix: [[f64; 4]; 4]) -> Self {
166 Self { matrix }
167 }
168
169 #[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 #[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}