zng_layout/unit/
transform.rs

1use super::{Px, euclid};
2
3use zng_var::{
4    animation::{Transitionable, easing::EasingStep, is_slerp_enabled, slerp_enabled},
5    impl_from_and_into_var,
6};
7
8use super::{AngleRadian, AngleUnits, Factor, FactorUnits, Layout1d, Length, PxTransform};
9
10/// A transform builder type.
11///
12/// # Builder
13///
14/// The transform can be started by one of `Transform::new_*` associated functions or [`Transform::identity`]. More
15/// transforms can be chained by calling the methods for each.
16///
17/// # Examples
18///
19/// Create a transform that
20///
21/// ```
22/// # use zng_layout::unit::*;
23/// let rotate_then_move = Transform::new_rotate(10.deg()).translate(50, 30);
24/// ```
25#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
26pub struct Transform {
27    parts: Vec<TransformPart>,
28    needs_layout: bool,
29    lerp_to: Vec<(Self, EasingStep, bool)>,
30}
31#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
32enum TransformPart {
33    Computed(PxTransform),
34    Translate(Length, Length),
35    Translate3d(Length, Length, Length),
36    Perspective(Length),
37}
38
39impl Transform {
40    /// No transform.
41    pub fn identity() -> Self {
42        Self::default()
43    }
44
45    /// Create a 2d rotation transform.
46    pub fn new_rotate<A: Into<AngleRadian>>(angle: A) -> Transform {
47        Transform::identity().rotate(angle)
48    }
49
50    /// Create a 3d rotation transform around the ***x*** axis.
51    pub fn new_rotate_x<A: Into<AngleRadian>>(angle: A) -> Transform {
52        Transform::identity().rotate_x(angle)
53    }
54
55    /// Create a 3d rotation transform around the ***y*** axis.
56    pub fn new_rotate_y<A: Into<AngleRadian>>(angle: A) -> Transform {
57        Transform::identity().rotate_y(angle)
58    }
59
60    /// Same as `new_rotate`.
61    pub fn new_rotate_z<A: Into<AngleRadian>>(angle: A) -> Transform {
62        Transform::identity().rotate_z(angle)
63    }
64
65    /// Create a 3d rotation transform.
66    pub fn new_rotate_3d<A: Into<AngleRadian>>(x: f32, y: f32, z: f32, angle: A) -> Transform {
67        Transform::identity().rotate_3d(x, y, z, angle)
68    }
69
70    /// Create a 2d translation transform.
71    pub fn new_translate<X: Into<Length>, Y: Into<Length>>(x: X, y: Y) -> Transform {
72        Transform::identity().translate(x, y)
73    }
74
75    /// Create a 3d translation transform.
76    pub fn new_translate_3d<X: Into<Length>, Y: Into<Length>, Z: Into<Length>>(x: X, y: Y, z: Z) -> Transform {
77        Transform::identity().translate_3d(x, y, z)
78    }
79
80    /// Create a 2d translation transform in the X dimension.
81    pub fn new_translate_x<X: Into<Length>>(x: X) -> Transform {
82        Transform::new_translate(x, 0.0)
83    }
84
85    /// Create a 2d translation transform in the Y dimension.
86    pub fn new_translate_y<Y: Into<Length>>(y: Y) -> Transform {
87        Transform::new_translate(0.0, y)
88    }
89
90    /// Create a 3d translation transform in the z dimension.
91    pub fn new_translate_z<Z: Into<Length>>(z: Z) -> Transform {
92        Transform::new_translate_3d(0.0, 0.0, z)
93    }
94
95    /// Create a 3d perspective transform.
96    pub fn new_perspective<D: Into<Length>>(d: D) -> Transform {
97        Transform::identity().perspective(d)
98    }
99
100    /// Create a 2d skew transform.
101    pub fn new_skew<X: Into<AngleRadian>, Y: Into<AngleRadian>>(x: X, y: Y) -> Transform {
102        Transform::identity().skew(x, y)
103    }
104
105    /// Create a 2d skew transform in the X dimension.
106    pub fn new_skew_x<X: Into<AngleRadian>>(x: X) -> Transform {
107        Transform::new_skew(x, 0.rad())
108    }
109
110    /// Create a 2d skew transform in the Y dimension.
111    pub fn new_skew_y<Y: Into<AngleRadian>>(y: Y) -> Transform {
112        Transform::new_skew(0.rad(), y)
113    }
114
115    /// Create a 2d scale transform.
116    ///
117    /// The same `scale` is applied to both dimensions.
118    pub fn new_scale<S: Into<Factor>>(scale: S) -> Transform {
119        let scale = scale.into();
120        Transform::new_scale_xy(scale, scale)
121    }
122
123    /// Create a 2d scale transform on the X dimension.
124    pub fn new_scale_x<X: Into<Factor>>(x: X) -> Transform {
125        Transform::new_scale_xy(x, 1.0)
126    }
127
128    /// Create a 2d scale transform on the Y dimension.
129    pub fn new_scale_y<Y: Into<Factor>>(y: Y) -> Transform {
130        Transform::new_scale_xy(1.0, y)
131    }
132
133    /// Create a 2d scale transform.
134    pub fn new_scale_xy<X: Into<Factor>, Y: Into<Factor>>(x: X, y: Y) -> Transform {
135        Transform::identity().scale_xy(x, y)
136    }
137}
138
139impl Transform {
140    /// Change `self` to apply `other` after its transformation.
141    ///
142    /// # Examples
143    ///
144    /// ```
145    /// # use zng_layout::unit::*;
146    /// Transform::new_rotate(10.deg()).then(Transform::new_translate(50, 30));
147    /// ```
148    ///
149    /// Is the equivalent of:
150    ///
151    /// ```
152    /// # use zng_layout::unit::*;
153    /// Transform::new_rotate(10.deg()).translate(50, 30);
154    /// ```
155    pub fn then(mut self, other: Transform) -> Self {
156        let mut other_parts = other.parts.into_iter();
157        self.needs_layout |= other.needs_layout;
158        if let Some(first) = other_parts.next() {
159            match first {
160                TransformPart::Computed(first) => self.then_transform(first),
161                first => self.parts.push(first),
162            }
163            self.parts.extend(other_parts);
164        }
165        self
166    }
167
168    fn then_transform(&mut self, transform: PxTransform) {
169        if let Some(TransformPart::Computed(last)) = self.parts.last_mut() {
170            *last = last.then(&transform);
171        } else {
172            self.parts.push(TransformPart::Computed(transform));
173        }
174    }
175
176    /// Change `self` to apply a 2d rotation after its transformation.
177    pub fn rotate<A: Into<AngleRadian>>(mut self, angle: A) -> Self {
178        self.then_transform(PxTransform::rotation(0.0, 0.0, angle.into().into()));
179        self
180    }
181
182    /// Change `self` to apply a 3d rotation around the ***x*** axis.
183    ///
184    /// Note that the composition of 3D rotations is usually not commutative, so the order this is applied will affect the result.
185    pub fn rotate_x<A: Into<AngleRadian>>(mut self, angle: A) -> Self {
186        self.then_transform(PxTransform::rotation_3d(1.0, 0.0, 0.0, angle.into().into()));
187        self
188    }
189
190    /// Change `self` to apply a 3d rotation around the ***y*** axis.
191    ///
192    /// Note that the composition of 3D rotations is usually not commutative, so the order this is applied will affect the result.
193    pub fn rotate_y<A: Into<AngleRadian>>(mut self, angle: A) -> Self {
194        self.then_transform(PxTransform::rotation_3d(0.0, 1.0, 0.0, angle.into().into()));
195        self
196    }
197
198    /// Same as [`rotate`].
199    ///
200    /// [`rotate`]: Self::rotate
201    pub fn rotate_z<A: Into<AngleRadian>>(mut self, angle: A) -> Self {
202        self.then_transform(PxTransform::rotation_3d(0.0, 0.0, 1.0, angle.into().into()));
203        self
204    }
205
206    /// Change `self` to apply a 3d rotation.
207    ///
208    /// Note that the composition of 3D rotations is usually not commutative, so the order this is applied will affect the result.
209    pub fn rotate_3d<A: Into<AngleRadian>>(mut self, x: f32, y: f32, z: f32, angle: A) -> Self {
210        self.then_transform(PxTransform::rotation_3d(x, y, z, angle.into().into()));
211        self
212    }
213
214    /// Change `self` to apply a 2d translation after its transformation.
215    pub fn translate<X: Into<Length>, Y: Into<Length>>(mut self, x: X, y: Y) -> Self {
216        self.parts.push(TransformPart::Translate(x.into(), y.into()));
217        self.needs_layout = true;
218        self
219    }
220    /// Change `self` to apply a ***x*** translation after its transformation.
221    pub fn translate_x<X: Into<Length>>(self, x: X) -> Self {
222        self.translate(x, 0.0)
223    }
224    /// Change `self` to apply a ***y*** translation after its transformation.
225    pub fn translate_y<Y: Into<Length>>(self, y: Y) -> Self {
226        self.translate(0.0, y)
227    }
228
229    /// Change `self` to apply a ***z*** translation after its transformation.
230    pub fn translate_z<Z: Into<Length>>(self, z: Z) -> Self {
231        self.translate_3d(0.0, 0.0, z)
232    }
233
234    /// Change `self` to apply a 3d translation after its transformation.
235    ///
236    /// Note that the composition of 3D rotations is usually not commutative, so the order this is applied will affect the result.
237    pub fn translate_3d<X: Into<Length>, Y: Into<Length>, Z: Into<Length>>(mut self, x: X, y: Y, z: Z) -> Self {
238        self.parts.push(TransformPart::Translate3d(x.into(), y.into(), z.into()));
239        self.needs_layout = true;
240        self
241    }
242
243    /// Change `self` to apply a 2d skew after its transformation.
244    pub fn skew<X: Into<AngleRadian>, Y: Into<AngleRadian>>(mut self, x: X, y: Y) -> Self {
245        self.then_transform(PxTransform::skew(x.into().into(), y.into().into()));
246        self
247    }
248    /// Change `self` to apply a ***x*** skew after its transformation.
249    pub fn skew_x<X: Into<AngleRadian>>(self, x: X) -> Self {
250        self.skew(x, 0.rad())
251    }
252    /// Change `self` to apply a ***y*** skew after its transformation.
253    pub fn skew_y<Y: Into<AngleRadian>>(self, y: Y) -> Self {
254        self.skew(0.rad(), y)
255    }
256
257    /// Change `self` to apply a 2d scale after its transformation.
258    pub fn scale_xy<X: Into<Factor>, Y: Into<Factor>>(mut self, x: X, y: Y) -> Self {
259        self.then_transform(PxTransform::scale(x.into().0, y.into().0));
260        self
261    }
262    /// Change `self` to apply a ***x*** scale after its transformation.
263    pub fn scale_x<X: Into<Factor>>(self, x: X) -> Self {
264        self.scale_xy(x, 1.0)
265    }
266    /// Change `self` to apply a ***y*** scale after its transformation.
267    pub fn scale_y<Y: Into<Factor>>(self, y: Y) -> Self {
268        self.scale_xy(1.0, y)
269    }
270    /// Change `self` to apply a uniform 2d scale after its transformation.
271    pub fn scale<S: Into<Factor>>(self, scale: S) -> Self {
272        let s = scale.into();
273        self.scale_xy(s, s)
274    }
275
276    /// Change `self` 3d perspective distance.
277    pub fn perspective<D: Into<Length>>(mut self, d: D) -> Self {
278        self.parts.push(TransformPart::Perspective(d.into()));
279        self.needs_layout = true;
280        self
281    }
282}
283impl Transform {
284    /// Compute a [`PxTransform`] in the current [`LAYOUT`] context.
285    ///
286    /// [`LAYOUT`]: crate::context::LAYOUT
287    pub fn layout(&self) -> PxTransform {
288        let mut r = PxTransform::identity();
289        for step in &self.parts {
290            r = match step {
291                TransformPart::Computed(m) => r.then(m),
292                TransformPart::Translate(x, y) => r.then(&PxTransform::translation(x.layout_f32_x(), y.layout_f32_y())),
293                TransformPart::Translate3d(x, y, z) => {
294                    r.then(&PxTransform::translation_3d(x.layout_f32_x(), y.layout_f32_y(), z.layout_f32_z()))
295                }
296                TransformPart::Perspective(d) => r.then(&PxTransform::perspective(d.layout_f32_z())),
297            };
298        }
299
300        for (to, step, slerp) in self.lerp_to.iter() {
301            let to = to.layout();
302            r = slerp_enabled(*slerp, || lerp_px_transform(r, &to, *step));
303        }
304
305        r
306    }
307
308    /// Compute a [`PxTransform`] if it is not affected by the layout context.
309    pub fn try_layout(&self) -> Option<PxTransform> {
310        if self.needs_layout {
311            return None;
312        }
313
314        let mut r = PxTransform::identity();
315        for step in &self.parts {
316            r = match step {
317                TransformPart::Computed(m) => r.then(m),
318                TransformPart::Translate(_, _) | TransformPart::Translate3d(_, _, _) | TransformPart::Perspective(_) => unreachable!(),
319            }
320        }
321        Some(r)
322    }
323
324    /// Returns `true` if this transform is affected by the layout context where it is evaluated.
325    pub fn needs_layout(&self) -> bool {
326        self.needs_layout
327    }
328}
329impl super::Layout2d for Transform {
330    type Px = PxTransform;
331
332    fn layout_dft(&self, _: Self::Px) -> Self::Px {
333        self.layout()
334    }
335
336    fn affect_mask(&self) -> super::LayoutMask {
337        let mut mask = super::LayoutMask::empty();
338        for part in &self.parts {
339            match part {
340                TransformPart::Computed(_) => {}
341                TransformPart::Translate(x, y) => {
342                    mask |= x.affect_mask();
343                    mask |= y.affect_mask();
344                }
345                TransformPart::Translate3d(x, y, z) => {
346                    mask |= x.affect_mask();
347                    mask |= y.affect_mask();
348                    mask |= z.affect_mask();
349                }
350                TransformPart::Perspective(d) => mask |= d.affect_mask(),
351            }
352        }
353        mask
354    }
355}
356
357impl Transitionable for Transform {
358    fn lerp(mut self, to: &Self, step: EasingStep) -> Self {
359        if step == 0.fct() {
360            self
361        } else if step == 1.fct() {
362            to.clone()
363        } else {
364            if self.needs_layout || to.needs_layout {
365                self.needs_layout = true;
366                self.lerp_to.push((to.clone(), step, is_slerp_enabled()));
367            } else {
368                let a = self.layout();
369                let b = to.layout();
370                self = Transform::from(lerp_px_transform(a, &b, step));
371            }
372            self
373        }
374    }
375}
376
377fn lerp_px_transform(s: PxTransform, to: &PxTransform, step: EasingStep) -> PxTransform {
378    if step == 0.fct() {
379        s
380    } else if step == 1.fct() {
381        *to
382    } else {
383        match (s, to) {
384            (PxTransform::Offset(from), PxTransform::Offset(to)) => PxTransform::Offset(from.lerp(*to, step.0)),
385            (from, to) => {
386                match (
387                    MatrixDecomposed3D::decompose(from.to_transform()),
388                    MatrixDecomposed3D::decompose(to.to_transform()),
389                ) {
390                    (Some(from), Some(to)) => {
391                        let l = from.lerp(&to, step);
392                        PxTransform::Transform(l.recompose())
393                    }
394                    _ => {
395                        if step.0 < 0.5 {
396                            s
397                        } else {
398                            *to
399                        }
400                    }
401                }
402            }
403        }
404    }
405}
406
407impl_from_and_into_var! {
408    fn from(t: PxTransform) -> Transform {
409        Transform {
410            parts: vec![TransformPart::Computed(t)],
411            needs_layout: false,
412            lerp_to: vec![],
413        }
414    }
415}
416
417// Matrix decomposition. Mostly copied from Servo code.
418// https://github.com/servo/servo/blob/master/components/style/values/animated/transform.rs
419
420type Scale = (f32, f32, f32);
421type Skew = (f32, f32, f32);
422type Perspective = (f32, f32, f32, f32);
423type Quaternion = euclid::Rotation3D<f64, (), ()>;
424
425/// A decomposed 3d matrix.
426#[derive(Clone, Copy, Debug, PartialEq)]
427struct MatrixDecomposed3D {
428    /// A translation function.
429    pub translate: euclid::Vector3D<f32, Px>,
430    /// A scale function.
431    pub scale: Scale,
432    /// The skew component of the transformation.
433    pub skew: Skew,
434    /// The perspective component of the transformation.
435    pub perspective: Perspective,
436    /// The quaternion used to represent the rotation.
437    pub quaternion: Quaternion,
438}
439impl MatrixDecomposed3D {
440    pub fn decompose(mut matrix: euclid::Transform3D<f32, Px, Px>) -> Option<Self> {
441        // Combine 2 point.
442        let combine = |a: [f32; 3], b: [f32; 3], ascl: f32, bscl: f32| {
443            [
444                (ascl * a[0]) + (bscl * b[0]),
445                (ascl * a[1]) + (bscl * b[1]),
446                (ascl * a[2]) + (bscl * b[2]),
447            ]
448        };
449        // Dot product.
450        let dot = |a: [f32; 3], b: [f32; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
451        // Cross product.
452        let cross = |row1: [f32; 3], row2: [f32; 3]| {
453            [
454                row1[1] * row2[2] - row1[2] * row2[1],
455                row1[2] * row2[0] - row1[0] * row2[2],
456                row1[0] * row2[1] - row1[1] * row2[0],
457            ]
458        };
459
460        if matrix.m44 == 0.0 {
461            return None;
462        }
463
464        let scaling_factor = matrix.m44;
465
466        // Normalize the matrix.
467        matrix_scale_by_factor(&mut matrix, 1.0 / scaling_factor);
468
469        // perspective_matrix is used to solve for perspective, but it also provides
470        // an easy way to test for singularity of the upper 3x3 component.
471        let mut perspective_matrix = matrix;
472
473        perspective_matrix.m14 = 0.0;
474        perspective_matrix.m24 = 0.0;
475        perspective_matrix.m34 = 0.0;
476        perspective_matrix.m44 = 1.0;
477
478        if perspective_matrix.determinant() == 0.0 {
479            return None;
480        }
481
482        // First, isolate perspective.
483        let perspective = if matrix.m14 != 0.0 || matrix.m24 != 0.0 || matrix.m34 != 0.0 {
484            let right_hand_side: [f32; 4] = [matrix.m14, matrix.m24, matrix.m34, matrix.m44];
485
486            perspective_matrix = matrix_transpose(perspective_matrix.inverse().unwrap());
487            let perspective = matrix_pre_mul_point4(&perspective_matrix, &right_hand_side);
488
489            (perspective[0], perspective[1], perspective[2], perspective[3])
490        } else {
491            (0.0, 0.0, 0.0, 1.0)
492        };
493
494        // Next take care of translation (easy).
495        let translate = euclid::Vector3D::new(matrix.m41, matrix.m42, matrix.m43);
496
497        // Now get scale and shear. 'row' is a 3 element array of 3 component vectors
498        let mut row = get_matrix_3x3_part(&matrix);
499
500        // Compute X scale factor and normalize first row.
501        let row0len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt();
502        let mut scale = (row0len, 0.0, 0.0);
503        row[0] = [row[0][0] / row0len, row[0][1] / row0len, row[0][2] / row0len];
504
505        // Compute XY shear factor and make 2nd row orthogonal to 1st.
506        let mut skew = (dot(row[0], row[1]), 0.0, 0.0);
507        row[1] = combine(row[1], row[0], 1.0, -skew.0);
508
509        // Now, compute Y scale and normalize 2nd row.
510        let row1len = (row[1][0] * row[1][0] + row[1][1] * row[1][1] + row[1][2] * row[1][2]).sqrt();
511        scale.1 = row1len;
512        row[1] = [row[1][0] / row1len, row[1][1] / row1len, row[1][2] / row1len];
513        skew.0 /= scale.1;
514
515        // Compute XZ and YZ shears, orthogonalize 3rd row
516        skew.1 = dot(row[0], row[2]);
517        row[2] = combine(row[2], row[0], 1.0, -skew.1);
518        skew.2 = dot(row[1], row[2]);
519        row[2] = combine(row[2], row[1], 1.0, -skew.2);
520
521        // Next, get Z scale and normalize 3rd row.
522        let row2len = (row[2][0] * row[2][0] + row[2][1] * row[2][1] + row[2][2] * row[2][2]).sqrt();
523        scale.2 = row2len;
524        row[2] = [row[2][0] / row2len, row[2][1] / row2len, row[2][2] / row2len];
525        skew.1 /= scale.2;
526        skew.2 /= scale.2;
527
528        // At this point, the matrix (in rows) is orthonormal.
529        // Check for a coordinate system flip. If the determinant
530        // is -1, then negate the matrix and the scaling factors.
531        if dot(row[0], cross(row[1], row[2])) < 0.0 {
532            scale.0 *= -1.0;
533            scale.1 *= -1.0;
534            scale.2 *= -1.0;
535
536            #[expect(clippy::needless_range_loop)]
537            for i in 0..3 {
538                row[i][0] *= -1.0;
539                row[i][1] *= -1.0;
540                row[i][2] *= -1.0;
541            }
542        }
543
544        // Now, get the rotations out.
545        let mut quaternion = Quaternion::quaternion(
546            0.5 * ((1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0) as f64).sqrt(),
547            0.5 * ((1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0) as f64).sqrt(),
548            0.5 * ((1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0) as f64).sqrt(),
549            0.5 * ((1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0) as f64).sqrt(),
550        );
551
552        if row[2][1] > row[1][2] {
553            quaternion.i = -quaternion.i
554        }
555        if row[0][2] > row[2][0] {
556            quaternion.j = -quaternion.j
557        }
558        if row[1][0] > row[0][1] {
559            quaternion.k = -quaternion.k
560        }
561
562        Some(MatrixDecomposed3D {
563            translate,
564            scale,
565            skew,
566            perspective,
567            quaternion,
568        })
569    }
570
571    pub fn recompose(self) -> euclid::Transform3D<f32, Px, Px> {
572        let mut matrix = euclid::Transform3D::identity();
573
574        // set perspective
575        matrix.m14 = self.perspective.0;
576        matrix.m24 = self.perspective.1;
577        matrix.m34 = self.perspective.2;
578        matrix.m44 = self.perspective.3;
579
580        // apply translate
581        matrix.m41 += self.translate.x * matrix.m11 + self.translate.y * matrix.m21 + self.translate.z * matrix.m31;
582        matrix.m42 += self.translate.x * matrix.m12 + self.translate.y * matrix.m22 + self.translate.z * matrix.m32;
583        matrix.m43 += self.translate.x * matrix.m13 + self.translate.y * matrix.m23 + self.translate.z * matrix.m33;
584
585        // apply rotation
586        {
587            let x = self.quaternion.i;
588            let y = self.quaternion.j;
589            let z = self.quaternion.k;
590            let w = self.quaternion.r;
591
592            // Construct a composite rotation matrix from the quaternion values
593            // rotationMatrix is a identity 4x4 matrix initially
594            let mut rotation_matrix = euclid::Transform3D::identity();
595            rotation_matrix.m11 = 1.0 - 2.0 * (y * y + z * z) as f32;
596            rotation_matrix.m12 = 2.0 * (x * y + z * w) as f32;
597            rotation_matrix.m13 = 2.0 * (x * z - y * w) as f32;
598            rotation_matrix.m21 = 2.0 * (x * y - z * w) as f32;
599            rotation_matrix.m22 = 1.0 - 2.0 * (x * x + z * z) as f32;
600            rotation_matrix.m23 = 2.0 * (y * z + x * w) as f32;
601            rotation_matrix.m31 = 2.0 * (x * z + y * w) as f32;
602            rotation_matrix.m32 = 2.0 * (y * z - x * w) as f32;
603            rotation_matrix.m33 = 1.0 - 2.0 * (x * x + y * y) as f32;
604
605            matrix = rotation_matrix.then(&matrix);
606        }
607
608        // Apply skew
609        {
610            let mut temp = euclid::Transform3D::identity();
611            if self.skew.2 != 0.0 {
612                temp.m32 = self.skew.2;
613                matrix = temp.then(&matrix);
614                temp.m32 = 0.0;
615            }
616
617            if self.skew.1 != 0.0 {
618                temp.m31 = self.skew.1;
619                matrix = temp.then(&matrix);
620                temp.m31 = 0.0;
621            }
622
623            if self.skew.0 != 0.0 {
624                temp.m21 = self.skew.0;
625                matrix = temp.then(&matrix);
626            }
627        }
628
629        // apply scale
630        matrix.m11 *= self.scale.0;
631        matrix.m12 *= self.scale.0;
632        matrix.m13 *= self.scale.0;
633        matrix.m21 *= self.scale.1;
634        matrix.m22 *= self.scale.1;
635        matrix.m23 *= self.scale.1;
636        matrix.m31 *= self.scale.2;
637        matrix.m32 *= self.scale.2;
638        matrix.m33 *= self.scale.2;
639
640        matrix
641    }
642
643    pub fn lerp_scale(from: Scale, to: Scale, step: EasingStep) -> Scale {
644        (from.0.lerp(&to.0, step), from.1.lerp(&to.1, step), from.2.lerp(&to.2, step))
645    }
646
647    pub fn lerp_skew(from: Skew, to: Skew, step: EasingStep) -> Skew {
648        (from.0.lerp(&to.0, step), from.1.lerp(&to.1, step), from.2.lerp(&to.2, step))
649    }
650
651    pub fn lerp_perspective(from: Perspective, to: Perspective, step: EasingStep) -> Perspective {
652        (
653            from.0.lerp(&to.0, step),
654            from.1.lerp(&to.1, step),
655            from.2.lerp(&to.2, step),
656            from.3.lerp(&to.3, step),
657        )
658    }
659
660    pub fn lerp_quaternion(from: Quaternion, to: Quaternion, step: EasingStep) -> Quaternion {
661        match is_slerp_enabled() {
662            false => from.lerp(&to, step.0 as _),
663            true => from.slerp(&to, step.0 as _),
664        }
665    }
666}
667impl Transitionable for MatrixDecomposed3D {
668    fn lerp(self, to: &Self, step: EasingStep) -> Self {
669        Self {
670            translate: self.translate.lerp(to.translate, step.0),
671            scale: Self::lerp_scale(self.scale, to.scale, step),
672            skew: Self::lerp_skew(self.skew, to.skew, step),
673            perspective: Self::lerp_perspective(self.perspective, to.perspective, step),
674            quaternion: Self::lerp_quaternion(self.quaternion, to.quaternion, step),
675        }
676    }
677}
678
679fn matrix_scale_by_factor(m: &mut euclid::Transform3D<f32, Px, Px>, scaling_factor: f32) {
680    m.m11 *= scaling_factor;
681    m.m12 *= scaling_factor;
682    m.m13 *= scaling_factor;
683    m.m14 *= scaling_factor;
684    m.m21 *= scaling_factor;
685    m.m22 *= scaling_factor;
686    m.m23 *= scaling_factor;
687    m.m24 *= scaling_factor;
688    m.m31 *= scaling_factor;
689    m.m32 *= scaling_factor;
690    m.m33 *= scaling_factor;
691    m.m34 *= scaling_factor;
692    m.m41 *= scaling_factor;
693    m.m42 *= scaling_factor;
694    m.m43 *= scaling_factor;
695    m.m44 *= scaling_factor;
696}
697
698fn matrix_transpose(m: euclid::Transform3D<f32, Px, Px>) -> euclid::Transform3D<f32, Px, Px> {
699    euclid::Transform3D {
700        m11: m.m11,
701        m12: m.m21,
702        m13: m.m31,
703        m14: m.m41,
704        m21: m.m12,
705        m22: m.m22,
706        m23: m.m32,
707        m24: m.m42,
708        m31: m.m13,
709        m32: m.m23,
710        m33: m.m33,
711        m34: m.m43,
712        m41: m.m14,
713        m42: m.m24,
714        m43: m.m34,
715        m44: m.m44,
716        _unit: std::marker::PhantomData,
717    }
718}
719
720fn matrix_pre_mul_point4(m: &euclid::Transform3D<f32, Px, Px>, pin: &[f32; 4]) -> [f32; 4] {
721    [
722        pin[0] * m.m11 + pin[1] * m.m21 + pin[2] * m.m31 + pin[3] * m.m41,
723        pin[0] * m.m12 + pin[1] * m.m22 + pin[2] * m.m32 + pin[3] * m.m42,
724        pin[0] * m.m13 + pin[1] * m.m23 + pin[2] * m.m33 + pin[3] * m.m43,
725        pin[0] * m.m14 + pin[1] * m.m24 + pin[2] * m.m34 + pin[3] * m.m44,
726    ]
727}
728
729fn get_matrix_3x3_part(&m: &euclid::Transform3D<f32, Px, Px>) -> [[f32; 3]; 3] {
730    [[m.m11, m.m12, m.m13], [m.m21, m.m22, m.m23], [m.m31, m.m32, m.m33]]
731}