1use num_traits::Float;
2
3use super::{Point3D, Quaternion3D};
4
5#[derive(Copy, Clone, Debug)]
7pub struct Transform3D<T> {
8 pub columns: [[T; 4]; 4],
10}
11
12impl<T> Transform3D<T> {
13 pub fn new(columns: [[T; 4]; 4]) -> Transform3D<T> {
18 Transform3D { columns }
19 }
20
21 pub fn map<F, O>(self, mut op: F) -> Transform3D<O>
24 where
25 F: FnMut(T) -> O,
26 {
27 let [[a00, a01, a02, a03], [a10, a11, a12, a13], [a20, a21, a22, a23], [a30, a31, a32, a33]] =
28 self.columns;
29
30 Transform3D {
31 columns: [
32 [op(a00), op(a01), op(a02), op(a03)],
33 [op(a10), op(a11), op(a12), op(a13)],
34 [op(a20), op(a21), op(a22), op(a23)],
35 [op(a30), op(a31), op(a32), op(a33)],
36 ],
37 }
38 }
39}
40
41impl<T> Transform3D<T>
42where
43 T: Float,
44{
45 pub fn identity() -> Transform3D<T> {
48 let i = T::one();
49 let o = T::zero();
50
51 Transform3D {
52 columns: [[i, o, o, o], [o, i, o, o], [o, o, i, o], [o, o, o, i]],
53 }
54 }
55
56 pub fn with_perspective(d: T) -> Transform3D<T> {
59 let mut columns = Self::identity().columns;
60 columns[2][3] = d.recip();
61 Transform3D { columns }
62 }
63
64 pub fn with_translation(tx: T, ty: T, tz: T) -> Transform3D<T> {
67 let mut columns = Self::identity().columns;
68 columns[3][0] = tx;
69 columns[3][1] = ty;
70 columns[3][2] = tz;
71 Transform3D { columns }
72 }
73
74 pub fn with_scale(sx: T, sy: T, sz: T) -> Transform3D<T> {
76 let mut columns = Self::identity().columns;
77 columns[0][0] = sx;
78 columns[1][1] = sy;
79 columns[2][2] = sz;
80 Transform3D { columns }
81 }
82
83 pub fn with_skew_x(sx: T) -> Transform3D<T> {
86 let mut columns = Self::identity().columns;
87 columns[1][0] = sx.tan();
88 Transform3D { columns }
89 }
90
91 pub fn with_skew_y(sy: T) -> Transform3D<T> {
94 let mut columns = Self::identity().columns;
95 columns[0][1] = sy.tan();
96 Transform3D { columns }
97 }
98
99 pub fn with_rotation(q: Quaternion3D<T>) -> Transform3D<T> {
105 let Quaternion3D { x, y, z, w } = q;
106
107 let one = T::one();
108 let two = one + one;
109
110 let mut columns = Self::identity().columns;
111 columns[0][0] = one - two * (y * y + z * z);
112 columns[0][1] = two * (x * y + z * w);
113 columns[0][2] = two * (x * z - y * w);
114 columns[1][0] = two * (x * y - z * w);
115 columns[1][1] = one - two * (x * x + z * z);
116 columns[1][2] = two * (y * z + x * w);
117 columns[2][0] = two * (x * z + y * w);
118 columns[2][1] = two * (y * z - x * w);
119 columns[2][2] = one - two * (x * x + y * y);
120
121 Transform3D { columns }
122 }
123
124 pub fn translate(self, tx: T, ty: T, tz: T) -> Transform3D<T> {
127 self.concat(Self::with_translation(tx, ty, tz))
128 }
129
130 pub fn scale(self, sx: T, sy: T, sz: T) -> Transform3D<T> {
133 self.concat(Self::with_scale(sx, sy, sz))
134 }
135
136 pub fn rotate(self, q: Quaternion3D<T>) -> Transform3D<T> {
141 self.concat(Self::with_rotation(q))
142 }
143
144 pub fn concat(self, other: Transform3D<T>) -> Transform3D<T> {
147 macro_rules! dot {
148 ($c:expr, $a:expr, $b:expr, $i:literal, $j:literal) => {
149 $c[$i][$j] = $a[$i][0] * $b[0][$j]
150 + $a[$i][1] * $b[1][$j]
151 + $a[$i][2] * $b[2][$j]
152 + $a[$i][3] * $b[3][$j];
153 };
154 }
155
156 let mut columns = Self::identity().columns;
157 dot!(columns, other.columns, self.columns, 0, 0);
158 dot!(columns, other.columns, self.columns, 0, 1);
159 dot!(columns, other.columns, self.columns, 0, 2);
160 dot!(columns, other.columns, self.columns, 0, 3);
161 dot!(columns, other.columns, self.columns, 1, 0);
162 dot!(columns, other.columns, self.columns, 1, 1);
163 dot!(columns, other.columns, self.columns, 1, 2);
164 dot!(columns, other.columns, self.columns, 1, 3);
165 dot!(columns, other.columns, self.columns, 2, 0);
166 dot!(columns, other.columns, self.columns, 2, 1);
167 dot!(columns, other.columns, self.columns, 2, 2);
168 dot!(columns, other.columns, self.columns, 2, 3);
169 dot!(columns, other.columns, self.columns, 3, 0);
170 dot!(columns, other.columns, self.columns, 3, 1);
171 dot!(columns, other.columns, self.columns, 3, 2);
172 dot!(columns, other.columns, self.columns, 3, 3);
173
174 Transform3D { columns }
175 }
176
177 pub fn apply(self, point: Point3D<T>) -> Point3D<T> {
179 let x = self.columns[0][0] * point.x
180 + self.columns[1][0] * point.y
181 + self.columns[2][0] * point.z
182 + self.columns[3][0];
183
184 let y = self.columns[0][1] * point.x
185 + self.columns[1][1] * point.y
186 + self.columns[2][1] * point.z
187 + self.columns[3][1];
188
189 let z = self.columns[0][2] * point.x
190 + self.columns[1][2] * point.y
191 + self.columns[2][2] * point.z
192 + self.columns[3][2];
193
194 let q = self.columns[0][3] * point.x
195 + self.columns[1][3] * point.y
196 + self.columns[2][3] * point.z
197 + self.columns[3][3];
198
199 Point3D {
200 x: x / q,
201 y: y / q,
202 z: z / q,
203 }
204 }
205
206 pub fn multiply_scalar(mut self, scalar: T) -> Transform3D<T> {
209 for i in 0..4 {
210 for j in 0..4 {
211 self.columns[i][j] = self.columns[i][j] * scalar;
212 }
213 }
214
215 self
216 }
217
218 fn subtract(self, other: Transform3D<T>) -> Transform3D<T> {
219 let mut columns = self.columns;
220 for i in 0..4 {
221 for j in 0..4 {
222 columns[i][j] = self.columns[i][j] - other.columns[i][j];
223 }
224 }
225 Transform3D { columns }
226 }
227
228 fn l2_norm(self) -> T {
229 (0..4).into_iter().fold(T::zero(), |acc, i| {
230 acc + (0..4).into_iter().fold(T::zero(), |acc, j| {
231 acc + self.columns[i][j] * self.columns[i][j]
232 })
233 })
234 }
235}
236
237impl<T> Default for Transform3D<T>
238where
239 T: Float,
240{
241 fn default() -> Self {
242 Transform3D::identity()
243 }
244}
245
246impl<T> Eq for Transform3D<T> where T: Float {}
247
248impl<T> PartialEq for Transform3D<T>
249where
250 T: Float,
251{
252 fn eq(&self, other: &Transform3D<T>) -> bool {
253 self.subtract(*other).l2_norm() < T::from(1.0 / 1000.0).unwrap()
254 }
255}