zng_layout/unit/
transform.rs

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