1use num_traits::Float;
2
3use super::algebra::{Combine, Cross, Dot, Vector};
4use super::LaplaceExpansion3D;
5use crate::linalg::{Quaternion3D, Transform3D};
6
7#[derive(Copy, Clone, Debug, Default, PartialEq)]
11pub struct Decomposition3D<T> {
12 pub translation: [T; 3],
14
15 pub scale: [T; 3],
17
18 pub skew: [T; 3],
20
21 pub perspective: [T; 4],
23
24 pub quaternion: Quaternion3D<T>,
26}
27
28impl<T> Decomposition3D<T> {
29 pub fn new(
32 translation: [T; 3],
33 scale: [T; 3],
34 skew: [T; 3],
35 perspective: [T; 4],
36 quaternion: Quaternion3D<T>,
37 ) -> Decomposition3D<T> {
38 Decomposition3D {
39 translation,
40 scale,
41 skew,
42 perspective,
43 quaternion,
44 }
45 }
46
47 pub fn map<F, O>(self, mut op: F) -> Decomposition3D<O>
51 where
52 F: FnMut(T) -> O,
53 {
54 let [tx, ty, tz] = self.translation;
55 let [sx, sy, sz] = self.scale;
56 let [skx, sky, skz] = self.skew;
57 let [px, py, pz, pw] = self.perspective;
58
59 Decomposition3D {
60 translation: [op(tx), op(ty), op(tz)],
61 scale: [op(sx), op(sy), op(sz)],
62 skew: [op(skx), op(sky), op(skz)],
63 perspective: [op(px), op(py), op(pz), op(pw)],
64 quaternion: self.quaternion.map(op),
65 }
66 }
67
68 pub fn as_ref(&self) -> Decomposition3D<&T> {
72 let [tx, ty, tz] = &self.translation;
73 let [sx, sy, sz] = &self.scale;
74 let [skx, sky, skz] = &self.skew;
75 let [px, py, pz, pw] = &self.perspective;
76
77 Decomposition3D {
78 translation: [tx, ty, tz],
79 scale: [sx, sy, sz],
80 skew: [skx, sky, skz],
81 perspective: [px, py, pz, pw],
82 quaternion: self.quaternion.as_ref(),
83 }
84 }
85}
86
87impl<T> Decomposition3D<T>
88where
89 T: Default + Float,
90{
91 pub fn decompose(mut matrix: Transform3D<T>) -> Option<Decomposition3D<T>> {
95 let mut result = Decomposition3D::default();
96
97 if matrix.columns[3][3].is_zero() {
102 return None;
103 }
104
105 for i in 0..4 {
106 for j in 0..4 {
107 matrix.columns[i][j] = matrix.columns[i][j] / matrix.columns[3][3];
108 }
109 }
110
111 let mut perspective_matrix = matrix;
115
116 for i in 0..3 {
117 perspective_matrix.columns[i][3] = T::zero();
118 }
119
120 perspective_matrix.columns[3][3] = T::one();
121
122 let perspective_matrix = LaplaceExpansion3D::new(&perspective_matrix);
123
124 if perspective_matrix.determinant().is_zero() {
125 return None;
126 }
127
128 if !matrix.columns[0][3].is_zero()
130 || !matrix.columns[1][3].is_zero()
131 || !matrix.columns[2][3].is_zero()
132 {
133 unimplemented!(
134 "Decomposing transformation matrices with a perspective is not yet implemented."
135 )
136 } else {
137 result.perspective = [T::zero(), T::zero(), T::zero(), T::one()];
139 }
140
141 for i in 0..3 {
143 result.translation[i] = matrix.columns[3][i];
144 }
145
146 let mut row = [
148 <[T; 3]>::default(),
149 <[T; 3]>::default(),
150 <[T; 3]>::default(),
151 ];
152
153 for i in 0..3 {
154 row[i][0] = matrix.columns[i][0];
155 row[i][1] = matrix.columns[i][1];
156 row[i][2] = matrix.columns[i][2];
157 }
158
159 result.scale[0] = row[0].length();
161 row[0] = row[0].normalize();
162
163 result.skew[0] = row[0].dot(&row[1]);
165 row[1] = row[1].combine(T::one(), &row[0], -result.skew[0]);
166
167 result.scale[1] = row[1].length();
169 row[1] = row[1].normalize();
170 result.skew[0] = result.skew[0] / result.scale[1];
171
172 result.skew[1] = row[0].dot(&row[2]);
174 row[2] = row[2].combine(T::one(), &row[0], -result.skew[1]);
175 result.skew[2] = row[1].dot(&row[2]);
176 row[2] = row[2].combine(T::one(), &row[1], -result.skew[2]);
177
178 result.scale[2] = row[2].length();
180 row[2] = row[2].normalize();
181 result.skew[1] = result.skew[1] / result.scale[2];
182 result.skew[2] = result.skew[2] / result.scale[2];
183
184 let pdum3 = row[1].cross(&row[2]);
188
189 if row[0].dot(&pdum3) < T::zero() {
190 for i in 0..3 {
191 result.scale[i] = result.scale[i].neg();
192 row[i][0] = row[i][0].neg();
193 row[i][1] = row[i][1].neg();
194 row[i][2] = row[i][2].neg();
195 }
196 }
197
198 let half = T::from(0.5).unwrap();
200 result.quaternion.x = half
201 * (T::one() + row[0][0] - row[1][1] - row[2][2])
202 .max(T::zero())
203 .sqrt();
204 result.quaternion.y = half
205 * (T::one() - row[0][0] + row[1][1] - row[2][2])
206 .max(T::zero())
207 .sqrt();
208 result.quaternion.z = half
209 * (T::one() - row[0][0] - row[1][1] + row[2][2])
210 .max(T::zero())
211 .sqrt();
212 result.quaternion.w = half
213 * (T::one() + row[0][0] + row[1][1] + row[2][2])
214 .max(T::zero())
215 .sqrt();
216
217 if row[2][1] > row[1][2] {
218 result.quaternion.x = result.quaternion.x.neg();
219 }
220
221 if row[0][2] > row[2][0] {
222 result.quaternion.y = result.quaternion.y.neg();
223 }
224
225 if row[1][0] > row[0][1] {
226 result.quaternion.z = result.quaternion.z.neg();
227 }
228
229 Some(result)
230 }
231
232 pub fn interpolate(&self, other: &Self, progress: T) -> Self {
237 let mut result = *self;
238
239 let alpha = T::one() - progress;
240 let beta = progress;
241
242 result.translation = result.translation.combine(alpha, &other.translation, beta);
243 result.scale = result.scale.combine(alpha, &other.scale, beta);
244 result.skew = result.skew.combine(alpha, &other.skew, beta);
245 result.perspective = result.perspective.combine(alpha, &other.perspective, beta);
246 result.quaternion = self.quaternion.mix(alpha, other.quaternion);
247
248 result
249 }
250
251 pub fn recompose(&self) -> Transform3D<T> {
253 let mut result = Transform3D::identity();
254
255 for i in 0..4 {
257 result.columns[i][3] = self.perspective[i];
258 }
259
260 for i in 0..4 {
262 for j in 0..3 {
263 result.columns[3][i] =
264 result.columns[3][i] + self.translation[j] * result.columns[j][i]
265 }
266 }
267
268 result = result.rotate(self.quaternion);
270
271 if !self.skew[2].is_zero() {
272 let mut temp = Transform3D::identity();
273 temp.columns[2][1] = self.skew[2];
274 result = result.concat(temp);
275 }
276
277 if !self.skew[1].is_zero() {
278 let mut temp = Transform3D::identity();
279 temp.columns[2][0] = self.skew[1];
280 result = result.concat(temp);
281 }
282
283 if !self.skew[0].is_zero() {
284 let mut temp = Transform3D::identity();
285 temp.columns[1][0] = self.skew[0];
286 result = result.concat(temp);
287 }
288
289 for i in 0..3 {
291 for j in 0..4 {
292 result.columns[i][j] = result.columns[i][j] * self.scale[i];
293 }
294 }
295
296 result
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use std::f32::consts::PI;
304
305 fn interpolate<T>(a: Transform3D<T>, b: Transform3D<T>, t: T) -> Transform3D<T>
306 where
307 T: Default + Float,
308 {
309 let ad = Decomposition3D::decompose(a).unwrap();
310 let bd = Decomposition3D::decompose(b).unwrap();
311 let cd = ad.interpolate(&bd, t);
312 cd.recompose()
313 }
314
315 #[test]
316 fn test_halfway_rotation() {
317 assert_eq!(
318 interpolate(
319 Transform3D::identity(),
320 Transform3D::with_rotation(Quaternion3D::with_angle(
321 90.0 / 180.0 * PI,
322 0.0,
323 0.0,
324 1.0
325 )),
326 0.0
327 ),
328 Transform3D::with_rotation(Quaternion3D::with_angle(0.0 / 180.0 * PI, 0.0, 0.0, 1.0))
329 );
330
331 assert_eq!(
332 interpolate(
333 Transform3D::identity(),
334 Transform3D::with_rotation(Quaternion3D::with_angle(
335 90.0 / 180.0 * PI,
336 0.0,
337 0.0,
338 1.0
339 )),
340 1.0
341 ),
342 Transform3D::with_rotation(Quaternion3D::with_angle(90.0 / 180.0 * PI, 0.0, 0.0, 1.0))
343 );
344 }
345
346 #[test]
347 fn test_halfway_rotation_nonunit() {
348 assert_eq!(
349 interpolate(
350 Transform3D::identity(),
351 Transform3D::with_rotation(Quaternion3D::with_angle(
352 90.0 / 180.0 * PI,
353 0.5,
354 0.3,
355 0.8
356 )),
357 0.5
358 ),
359 Transform3D::with_rotation(Quaternion3D::with_angle(45.0 / 180.0 * PI, 0.5, 0.3, 0.8))
360 );
361 }
362
363 #[test]
364 fn test_halfway_translation() {
365 assert_eq!(
366 interpolate(
367 Transform3D::identity(),
368 Transform3D::with_translation(160.0, 20.0, 10.0),
369 0.5
370 ),
371 Transform3D::with_translation(80.0, 10.0, 5.0)
372 );
373
374 assert_eq!(
375 interpolate(
376 Transform3D::with_translation(160.0, 20.0, 10.0),
377 Transform3D::identity(),
378 0.5
379 ),
380 Transform3D::with_translation(80.0, 10.0, 5.0)
381 );
382
383 assert_eq!(
384 interpolate(
385 Transform3D::identity(),
386 Transform3D::with_translation(-160.0, -20.0, 10.0),
387 0.5
388 ),
389 Transform3D::with_translation(-80.0, -10.0, 5.0)
390 );
391
392 assert_eq!(
393 interpolate(
394 Transform3D::with_translation(-160.0, -20.0, 10.0),
395 Transform3D::identity(),
396 0.5
397 ),
398 Transform3D::with_translation(-80.0, -10.0, 5.0)
399 );
400 }
401
402 #[test]
403 fn test_decomposition_1() {
404 let transform = Transform3D::with_translation(160.0, 20.0, 10.0)
405 .rotate(Quaternion3D::with_angle(90.0 / 180.0 * PI, 0.0, 0.0, 1.0));
406
407 let decomposition = Decomposition3D::decompose(transform).unwrap();
408
409 let distance = decomposition.quaternion.subtract(&Quaternion3D::with_angle(
410 90.0 / 180.0 * PI,
411 0.0,
412 0.0,
413 1.0,
414 ));
415
416 assert!(distance.dot(&distance) <= 0.001);
417 }
418
419 #[test]
420 fn test_decomposition_2() {
421 let transform = Transform3D::with_translation(160.0, 20.0, 10.0)
422 .rotate(Quaternion3D::with_angle(90.0 / 180.0 * PI, 0.0, 0.0, 1.0));
423
424 let decomposition = Decomposition3D::decompose(transform).unwrap();
425
426 assert_eq!(decomposition.translation, [160.0, 20.0, 10.0]);
427
428 let distance = decomposition.quaternion.subtract(&Quaternion3D::with_angle(
429 90.0 / 180.0 * PI,
430 0.0,
431 0.0,
432 1.0,
433 ));
434
435 assert!(distance.dot(&distance) <= 0.001);
436 }
437
438 #[test]
439 fn test_interpolation() {
440 let start = Transform3D::identity();
441 let end = Transform3D::with_translation(160.0, 20.0, 10.0)
442 .rotate(Quaternion3D::with_angle(90.0 / 180.0 * PI, 0.0, 0.0, 1.0));
443
444 let start = Decomposition3D::decompose(start).unwrap();
445 let end = Decomposition3D::decompose(end).unwrap();
446
447 let mid = start.interpolate(&end, 0.5);
448
449 assert_eq!(mid.translation, [80.0, 10.0, 5.0]);
450
451 let distance =
452 mid.quaternion
453 .subtract(&Quaternion3D::with_angle(45.0 / 180.0 * PI, 0.0, 0.0, 1.0));
454
455 assert!(distance.dot(&distance) <= 0.001);
456 }
457
458 #[test]
459 fn test_complex() {
460 assert_eq!(
461 interpolate(
462 Transform3D::identity(),
463 Transform3D::with_translation(160.0, 20.0, 10.0).rotate(Quaternion3D::with_angle(
464 90.0 / 180.0 * PI,
465 0.0,
466 0.0,
467 1.0
468 )),
469 0.5
470 ),
471 Transform3D::with_translation(80.0, 10.0, 5.0).rotate(Quaternion3D::with_angle(
472 45.0 / 180.0 * PI,
473 0.0,
474 0.0,
475 1.0
476 ))
477 );
478 }
479}