Skip to main content

style/values/animated/
transform.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Animated types for transform.
6// There are still some implementation on Matrix3D in animated_properties.mako.rs
7// because they still need mako to generate the code.
8
9use super::animate_multiplicative_factor;
10use super::{Animate, Procedure, ToAnimatedZero};
11use crate::derives::*;
12use crate::values::computed::transform::Rotate as ComputedRotate;
13use crate::values::computed::transform::Scale as ComputedScale;
14use crate::values::computed::transform::Transform as ComputedTransform;
15use crate::values::computed::transform::TransformOperation as ComputedTransformOperation;
16use crate::values::computed::transform::Translate as ComputedTranslate;
17use crate::values::computed::transform::{DirectionVector, Matrix, Matrix3D};
18use crate::values::computed::Angle;
19use crate::values::computed::{Length, LengthPercentage};
20use crate::values::computed::{Number, Percentage};
21use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
22use crate::values::generics::transform::{self, Transform, TransformOperation};
23use crate::values::generics::transform::{Rotate, Scale, Translate};
24use crate::values::CSSFloat;
25use crate::Zero;
26use std::cmp;
27use std::ops::Add;
28
29// ------------------------------------
30// Animations for Matrix/Matrix3D.
31// ------------------------------------
32/// A 2d matrix for interpolation.
33#[derive(Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf)]
34#[allow(missing_docs)]
35// FIXME: We use custom derive for ComputeSquaredDistance. However, If possible, we should convert
36// the InnerMatrix2D into types with physical meaning. This custom derive computes the squared
37// distance from each matrix item, and this makes the result different from that in Gecko if we
38// have skew factor in the Matrix3D.
39pub struct InnerMatrix2D {
40    pub m11: CSSFloat,
41    pub m12: CSSFloat,
42    pub m21: CSSFloat,
43    pub m22: CSSFloat,
44}
45
46impl Animate for InnerMatrix2D {
47    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
48        Ok(InnerMatrix2D {
49            m11: animate_multiplicative_factor(self.m11, other.m11, procedure)?,
50            m12: self.m12.animate(&other.m12, procedure)?,
51            m21: self.m21.animate(&other.m21, procedure)?,
52            m22: animate_multiplicative_factor(self.m22, other.m22, procedure)?,
53        })
54    }
55}
56
57/// A 2d translation function.
58#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf)]
59pub struct Translate2D(f32, f32);
60
61/// A 2d scale function.
62#[derive(Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf)]
63pub struct Scale2D(f32, f32);
64
65impl Animate for Scale2D {
66    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
67        Ok(Scale2D(
68            animate_multiplicative_factor(self.0, other.0, procedure)?,
69            animate_multiplicative_factor(self.1, other.1, procedure)?,
70        ))
71    }
72}
73
74/// A decomposed 2d matrix.
75#[derive(Clone, Copy, Debug, MallocSizeOf)]
76pub struct MatrixDecomposed2D {
77    /// The translation function.
78    pub translate: Translate2D,
79    /// The scale function.
80    pub scale: Scale2D,
81    /// The rotation angle.
82    pub angle: f32,
83    /// The inner matrix.
84    pub matrix: InnerMatrix2D,
85}
86
87impl Animate for MatrixDecomposed2D {
88    /// <https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-2d-matrix-values>
89    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
90        // If x-axis of one is flipped, and y-axis of the other,
91        // convert to an unflipped rotation.
92        let mut scale = self.scale;
93        let mut angle = self.angle;
94        let mut other_angle = other.angle;
95        if (scale.0 < 0.0 && other.scale.1 < 0.0) || (scale.1 < 0.0 && other.scale.0 < 0.0) {
96            scale.0 = -scale.0;
97            scale.1 = -scale.1;
98            angle += if angle < 0.0 { 180. } else { -180. };
99        }
100
101        // Don't rotate the long way around.
102        if angle == 0.0 {
103            angle = 360.
104        }
105        if other_angle == 0.0 {
106            other_angle = 360.
107        }
108
109        if (angle - other_angle).abs() > 180. {
110            if angle > other_angle {
111                angle -= 360.
112            } else {
113                other_angle -= 360.
114            }
115        }
116
117        // Interpolate all values.
118        let translate = self.translate.animate(&other.translate, procedure)?;
119        let scale = scale.animate(&other.scale, procedure)?;
120        let angle = angle.animate(&other_angle, procedure)?;
121        let matrix = self.matrix.animate(&other.matrix, procedure)?;
122
123        Ok(MatrixDecomposed2D {
124            translate,
125            scale,
126            angle,
127            matrix,
128        })
129    }
130}
131
132impl ComputeSquaredDistance for MatrixDecomposed2D {
133    #[inline]
134    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
135        // Use Radian to compute the distance.
136        const RAD_PER_DEG: f64 = std::f64::consts::PI / 180.0;
137        let angle1 = self.angle as f64 * RAD_PER_DEG;
138        let angle2 = other.angle as f64 * RAD_PER_DEG;
139        Ok(self.translate.compute_squared_distance(&other.translate)?
140            + self.scale.compute_squared_distance(&other.scale)?
141            + angle1.compute_squared_distance(&angle2)?
142            + self.matrix.compute_squared_distance(&other.matrix)?)
143    }
144}
145
146impl From<Matrix3D> for MatrixDecomposed2D {
147    /// Decompose a 2D matrix.
148    /// <https://drafts.csswg.org/css-transforms/#decomposing-a-2d-matrix>
149    fn from(matrix: Matrix3D) -> MatrixDecomposed2D {
150        let mut row0x = matrix.m11;
151        let mut row0y = matrix.m12;
152        let mut row1x = matrix.m21;
153        let mut row1y = matrix.m22;
154
155        let translate = Translate2D(matrix.m41, matrix.m42);
156        let mut scale = Scale2D(
157            (row0x * row0x + row0y * row0y).sqrt(),
158            (row1x * row1x + row1y * row1y).sqrt(),
159        );
160
161        // If determinant is negative, one axis was flipped.
162        let determinant = row0x * row1y - row0y * row1x;
163        if determinant < 0. {
164            if row0x < row1y {
165                scale.0 = -scale.0;
166            } else {
167                scale.1 = -scale.1;
168            }
169        }
170
171        // Renormalize matrix to remove scale.
172        if scale.0 != 0.0 {
173            row0x *= 1. / scale.0;
174            row0y *= 1. / scale.0;
175        }
176        if scale.1 != 0.0 {
177            row1x *= 1. / scale.1;
178            row1y *= 1. / scale.1;
179        }
180
181        // Compute rotation and renormalize matrix.
182        let mut angle = row0y.atan2(row0x);
183        if angle != 0.0 {
184            let sn = -row0y;
185            let cs = row0x;
186            let m11 = row0x;
187            let m12 = row0y;
188            let m21 = row1x;
189            let m22 = row1y;
190            row0x = cs * m11 + sn * m21;
191            row0y = cs * m12 + sn * m22;
192            row1x = -sn * m11 + cs * m21;
193            row1y = -sn * m12 + cs * m22;
194        }
195
196        let m = InnerMatrix2D {
197            m11: row0x,
198            m12: row0y,
199            m21: row1x,
200            m22: row1y,
201        };
202
203        // Convert into degrees because our rotation functions expect it.
204        angle = angle.to_degrees();
205        MatrixDecomposed2D {
206            translate,
207            scale,
208            angle,
209            matrix: m,
210        }
211    }
212}
213
214impl From<MatrixDecomposed2D> for Matrix3D {
215    /// Recompose a 2D matrix.
216    /// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-2d-matrix>
217    fn from(decomposed: MatrixDecomposed2D) -> Matrix3D {
218        let mut computed_matrix = Matrix3D::identity();
219        computed_matrix.m11 = decomposed.matrix.m11;
220        computed_matrix.m12 = decomposed.matrix.m12;
221        computed_matrix.m21 = decomposed.matrix.m21;
222        computed_matrix.m22 = decomposed.matrix.m22;
223
224        // Translate matrix.
225        computed_matrix.m41 = decomposed.translate.0;
226        computed_matrix.m42 = decomposed.translate.1;
227
228        // Rotate matrix.
229        let angle = decomposed.angle.to_radians();
230        let cos_angle = angle.cos();
231        let sin_angle = angle.sin();
232
233        let mut rotate_matrix = Matrix3D::identity();
234        rotate_matrix.m11 = cos_angle;
235        rotate_matrix.m12 = sin_angle;
236        rotate_matrix.m21 = -sin_angle;
237        rotate_matrix.m22 = cos_angle;
238
239        // Multiplication of computed_matrix and rotate_matrix
240        computed_matrix = rotate_matrix.multiply(&computed_matrix);
241
242        // Scale matrix.
243        computed_matrix.m11 *= decomposed.scale.0;
244        computed_matrix.m12 *= decomposed.scale.0;
245        computed_matrix.m21 *= decomposed.scale.1;
246        computed_matrix.m22 *= decomposed.scale.1;
247        computed_matrix
248    }
249}
250
251impl Animate for Matrix {
252    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
253        let this = Matrix3D::from(*self);
254        let other = Matrix3D::from(*other);
255        let from = decompose_2d_matrix(&this)?;
256        let to = decompose_2d_matrix(&other)?;
257        Matrix3D::from(from.animate(&to, procedure)?).into_2d()
258    }
259}
260
261/// A 3d translation.
262#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf)]
263pub struct Translate3D(pub f32, pub f32, pub f32);
264
265/// A 3d scale function.
266#[derive(Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf)]
267pub struct Scale3D(pub f32, pub f32, pub f32);
268
269impl Scale3D {
270    /// Negate self.
271    fn negate(&mut self) {
272        self.0 *= -1.0;
273        self.1 *= -1.0;
274        self.2 *= -1.0;
275    }
276}
277
278impl Animate for Scale3D {
279    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
280        Ok(Scale3D(
281            animate_multiplicative_factor(self.0, other.0, procedure)?,
282            animate_multiplicative_factor(self.1, other.1, procedure)?,
283            animate_multiplicative_factor(self.2, other.2, procedure)?,
284        ))
285    }
286}
287
288/// A 3d skew function.
289#[derive(Animate, Clone, Copy, Debug, MallocSizeOf)]
290pub struct Skew(f32, f32, f32);
291
292impl ComputeSquaredDistance for Skew {
293    // We have to use atan() to convert the skew factors into skew angles, so implement
294    // ComputeSquaredDistance manually.
295    #[inline]
296    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
297        Ok(self.0.atan().compute_squared_distance(&other.0.atan())?
298            + self.1.atan().compute_squared_distance(&other.1.atan())?
299            + self.2.atan().compute_squared_distance(&other.2.atan())?)
300    }
301}
302
303/// A 3d perspective transformation.
304#[derive(Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf)]
305pub struct Perspective(pub f32, pub f32, pub f32, pub f32);
306
307impl Animate for Perspective {
308    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
309        Ok(Perspective(
310            self.0.animate(&other.0, procedure)?,
311            self.1.animate(&other.1, procedure)?,
312            self.2.animate(&other.2, procedure)?,
313            animate_multiplicative_factor(self.3, other.3, procedure)?,
314        ))
315    }
316}
317
318/// A quaternion used to represent a rotation.
319#[derive(Clone, Copy, Debug, MallocSizeOf)]
320pub struct Quaternion(f64, f64, f64, f64);
321
322impl Quaternion {
323    /// Return a quaternion from a unit direction vector and angle (unit: radian).
324    #[inline]
325    fn from_direction_and_angle(vector: &DirectionVector, angle: f64) -> Self {
326        debug_assert!(
327            (vector.length() - 1.).abs() < 0.0001,
328            "Only accept an unit direction vector to create a quaternion"
329        );
330
331        // Quaternions between the range [360, 720] will treated as rotations at the other
332        // direction: [-360, 0]. And quaternions between the range [720*k, 720*(k+1)] will be
333        // treated as rotations [0, 720]. So it does not make sense to use quaternions to rotate
334        // the element more than ±360deg. Therefore, we have to make sure its range is (-360, 360).
335        let half_angle = angle
336            .abs()
337            .rem_euclid(std::f64::consts::TAU)
338            .copysign(angle)
339            / 2.;
340
341        // Reference:
342        // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation
343        //
344        // if the direction axis is (x, y, z) = xi + yj + zk,
345        // and the angle is |theta|, this formula can be done using
346        // an extension of Euler's formula:
347        //   q = cos(theta/2) + (xi + yj + zk)(sin(theta/2))
348        //     = cos(theta/2) +
349        //       x*sin(theta/2)i + y*sin(theta/2)j + z*sin(theta/2)k
350        Quaternion(
351            vector.x as f64 * half_angle.sin(),
352            vector.y as f64 * half_angle.sin(),
353            vector.z as f64 * half_angle.sin(),
354            half_angle.cos(),
355        )
356    }
357
358    /// Calculate the dot product.
359    #[inline]
360    fn dot(&self, other: &Self) -> f64 {
361        self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3
362    }
363
364    /// Return the scaled quaternion by a factor.
365    #[inline]
366    fn scale(&self, factor: f64) -> Self {
367        Quaternion(
368            self.0 * factor,
369            self.1 * factor,
370            self.2 * factor,
371            self.3 * factor,
372        )
373    }
374}
375
376impl Add for Quaternion {
377    type Output = Self;
378
379    fn add(self, other: Self) -> Self {
380        Self(
381            self.0 + other.0,
382            self.1 + other.1,
383            self.2 + other.2,
384            self.3 + other.3,
385        )
386    }
387}
388
389impl Animate for Quaternion {
390    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
391        let (this_weight, other_weight) = procedure.weights();
392        debug_assert!(
393            // Doule EPSILON since both this_weight and other_weght have calculation errors
394            // which are approximately equal to EPSILON.
395            (this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON * 2.0
396                || other_weight == 1.0f64
397                || other_weight == 0.0f64,
398            "animate should only be used for interpolating or accumulating transforms"
399        );
400
401        // We take a specialized code path for accumulation (where other_weight
402        // is 1).
403        if let Procedure::Accumulate { .. } = procedure {
404            debug_assert_eq!(other_weight, 1.0);
405            if this_weight == 0.0 {
406                return Ok(*other);
407            }
408
409            let clamped_w = self.3.min(1.0).max(-1.0);
410
411            // Determine the scale factor.
412            let mut theta = clamped_w.acos();
413            let mut scale = if theta == 0.0 { 0.0 } else { 1.0 / theta.sin() };
414            theta *= this_weight;
415            scale *= theta.sin();
416
417            // Scale the self matrix by this_weight.
418            let mut scaled_self = *self;
419            scaled_self.0 *= scale;
420            scaled_self.1 *= scale;
421            scaled_self.2 *= scale;
422            scaled_self.3 = theta.cos();
423
424            // Multiply scaled-self by other.
425            let a = &scaled_self;
426            let b = other;
427            return Ok(Quaternion(
428                a.3 * b.0 + a.0 * b.3 + a.1 * b.2 - a.2 * b.1,
429                a.3 * b.1 - a.0 * b.2 + a.1 * b.3 + a.2 * b.0,
430                a.3 * b.2 + a.0 * b.1 - a.1 * b.0 + a.2 * b.3,
431                a.3 * b.3 - a.0 * b.0 - a.1 * b.1 - a.2 * b.2,
432            ));
433        }
434
435        // https://drafts.csswg.org/css-transforms-2/#interpolation-of-decomposed-3d-matrix-values
436        //
437        // Dot product, clamped between -1 and 1.
438        let cos_half_theta =
439            (self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3)
440                .min(1.0)
441                .max(-1.0);
442
443        if cos_half_theta.abs() == 1.0 {
444            return Ok(*self);
445        }
446
447        let half_theta = cos_half_theta.acos();
448        let sin_half_theta = (1.0 - cos_half_theta * cos_half_theta).sqrt();
449
450        let right_weight = (other_weight * half_theta).sin() / sin_half_theta;
451        // The spec would like to use
452        // "(other_weight * half_theta).cos() - cos_half_theta * right_weight". However, this
453        // formula may produce some precision issues of floating-point number calculation, e.g.
454        // when the progress is 100% (i.e. |other_weight| is 1), the |left_weight| may not be
455        // perfectly equal to 0. It could be something like -2.22e-16, which is approximately equal
456        // to zero, in the test. And after we recompose the Matrix3D, these approximated zeros
457        // make us failed to treat this Matrix3D as a Matrix2D, when serializating it.
458        //
459        // Therefore, we use another formula to calculate |left_weight| here. Blink and WebKit also
460        // use this formula, which is defined in:
461        // https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/index.htm
462        // https://github.com/w3c/csswg-drafts/issues/9338
463        let left_weight = (this_weight * half_theta).sin() / sin_half_theta;
464
465        Ok(self.scale(left_weight) + other.scale(right_weight))
466    }
467}
468
469impl ComputeSquaredDistance for Quaternion {
470    #[inline]
471    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
472        // Use quaternion vectors to get the angle difference. Both q1 and q2 are unit vectors,
473        // so we can get their angle difference by:
474        // cos(theta/2) = (q1 dot q2) / (|q1| * |q2|) = q1 dot q2.
475        let distance = self.dot(other).max(-1.0).min(1.0).acos() * 2.0;
476        Ok(SquaredDistance::from_sqrt(distance))
477    }
478}
479
480/// A decomposed 3d matrix.
481#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf)]
482pub struct MatrixDecomposed3D {
483    /// A translation function.
484    pub translate: Translate3D,
485    /// A scale function.
486    pub scale: Scale3D,
487    /// The skew component of the transformation.
488    pub skew: Skew,
489    /// The perspective component of the transformation.
490    pub perspective: Perspective,
491    /// The quaternion used to represent the rotation.
492    pub quaternion: Quaternion,
493}
494
495impl From<MatrixDecomposed3D> for Matrix3D {
496    /// Recompose a 3D matrix.
497    /// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-3d-matrix>
498    fn from(decomposed: MatrixDecomposed3D) -> Matrix3D {
499        let mut matrix = Matrix3D::identity();
500
501        // Apply perspective
502        matrix.set_perspective(&decomposed.perspective);
503
504        // Apply translation
505        matrix.apply_translate(&decomposed.translate);
506
507        // Apply rotation
508        {
509            let x = decomposed.quaternion.0;
510            let y = decomposed.quaternion.1;
511            let z = decomposed.quaternion.2;
512            let w = decomposed.quaternion.3;
513
514            // Construct a composite rotation matrix from the quaternion values
515            // rotationMatrix is a identity 4x4 matrix initially
516            let mut rotation_matrix = Matrix3D::identity();
517            rotation_matrix.m11 = 1.0 - 2.0 * (y * y + z * z) as f32;
518            rotation_matrix.m12 = 2.0 * (x * y + z * w) as f32;
519            rotation_matrix.m13 = 2.0 * (x * z - y * w) as f32;
520            rotation_matrix.m21 = 2.0 * (x * y - z * w) as f32;
521            rotation_matrix.m22 = 1.0 - 2.0 * (x * x + z * z) as f32;
522            rotation_matrix.m23 = 2.0 * (y * z + x * w) as f32;
523            rotation_matrix.m31 = 2.0 * (x * z + y * w) as f32;
524            rotation_matrix.m32 = 2.0 * (y * z - x * w) as f32;
525            rotation_matrix.m33 = 1.0 - 2.0 * (x * x + y * y) as f32;
526
527            matrix = rotation_matrix.multiply(&matrix);
528        }
529
530        // Apply skew
531        {
532            let mut temp = Matrix3D::identity();
533            if decomposed.skew.2 != 0.0 {
534                temp.m32 = decomposed.skew.2;
535                matrix = temp.multiply(&matrix);
536                temp.m32 = 0.0;
537            }
538
539            if decomposed.skew.1 != 0.0 {
540                temp.m31 = decomposed.skew.1;
541                matrix = temp.multiply(&matrix);
542                temp.m31 = 0.0;
543            }
544
545            if decomposed.skew.0 != 0.0 {
546                temp.m21 = decomposed.skew.0;
547                matrix = temp.multiply(&matrix);
548            }
549        }
550
551        // Apply scale
552        matrix.apply_scale(&decomposed.scale);
553
554        matrix
555    }
556}
557
558/// Decompose a 3D matrix.
559/// https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix
560/// http://www.realtimerendering.com/resources/GraphicsGems/gemsii/unmatrix.c
561fn decompose_3d_matrix(mut matrix: Matrix3D) -> Result<MatrixDecomposed3D, ()> {
562    // Combine 2 point.
563    let combine = |a: [f32; 3], b: [f32; 3], ascl: f32, bscl: f32| {
564        [
565            (ascl * a[0]) + (bscl * b[0]),
566            (ascl * a[1]) + (bscl * b[1]),
567            (ascl * a[2]) + (bscl * b[2]),
568        ]
569    };
570    // Dot product.
571    let dot = |a: [f32; 3], b: [f32; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
572    // Cross product.
573    let cross = |row1: [f32; 3], row2: [f32; 3]| {
574        [
575            row1[1] * row2[2] - row1[2] * row2[1],
576            row1[2] * row2[0] - row1[0] * row2[2],
577            row1[0] * row2[1] - row1[1] * row2[0],
578        ]
579    };
580
581    if matrix.m44 == 0.0 {
582        return Err(());
583    }
584
585    let scaling_factor = matrix.m44;
586
587    // Normalize the matrix.
588    matrix.scale_by_factor(1.0 / scaling_factor);
589
590    // perspective_matrix is used to solve for perspective, but it also provides
591    // an easy way to test for singularity of the upper 3x3 component.
592    let mut perspective_matrix = matrix;
593
594    perspective_matrix.m14 = 0.0;
595    perspective_matrix.m24 = 0.0;
596    perspective_matrix.m34 = 0.0;
597    perspective_matrix.m44 = 1.0;
598
599    if perspective_matrix.determinant() == 0.0 {
600        return Err(());
601    }
602
603    // First, isolate perspective.
604    let perspective = if matrix.m14 != 0.0 || matrix.m24 != 0.0 || matrix.m34 != 0.0 {
605        let right_hand_side: [f32; 4] = [matrix.m14, matrix.m24, matrix.m34, matrix.m44];
606
607        perspective_matrix = perspective_matrix.inverse().unwrap().transpose();
608        let perspective = perspective_matrix.pre_mul_point4(&right_hand_side);
609        // NOTE(emilio): Even though the reference algorithm clears the
610        // fourth column here (matrix.m14..matrix.m44), they're not used below
611        // so it's not really needed.
612        Perspective(
613            perspective[0],
614            perspective[1],
615            perspective[2],
616            perspective[3],
617        )
618    } else {
619        Perspective(0.0, 0.0, 0.0, 1.0)
620    };
621
622    // Next take care of translation (easy).
623    let translate = Translate3D(matrix.m41, matrix.m42, matrix.m43);
624
625    // Now get scale and shear. 'row' is a 3 element array of 3 component vectors
626    let mut row = matrix.get_matrix_3x3_part();
627
628    // Compute X scale factor and normalize first row.
629    let row0len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt();
630    let mut scale = Scale3D(row0len, 0.0, 0.0);
631    row[0] = [
632        row[0][0] / row0len,
633        row[0][1] / row0len,
634        row[0][2] / row0len,
635    ];
636
637    // Compute XY shear factor and make 2nd row orthogonal to 1st.
638    let mut skew = Skew(dot(row[0], row[1]), 0.0, 0.0);
639    row[1] = combine(row[1], row[0], 1.0, -skew.0);
640
641    // Now, compute Y scale and normalize 2nd row.
642    let row1len = (row[1][0] * row[1][0] + row[1][1] * row[1][1] + row[1][2] * row[1][2]).sqrt();
643    scale.1 = row1len;
644    row[1] = [
645        row[1][0] / row1len,
646        row[1][1] / row1len,
647        row[1][2] / row1len,
648    ];
649    skew.0 /= scale.1;
650
651    // Compute XZ and YZ shears, orthogonalize 3rd row
652    skew.1 = dot(row[0], row[2]);
653    row[2] = combine(row[2], row[0], 1.0, -skew.1);
654    skew.2 = dot(row[1], row[2]);
655    row[2] = combine(row[2], row[1], 1.0, -skew.2);
656
657    // Next, get Z scale and normalize 3rd row.
658    let row2len = (row[2][0] * row[2][0] + row[2][1] * row[2][1] + row[2][2] * row[2][2]).sqrt();
659    scale.2 = row2len;
660    row[2] = [
661        row[2][0] / row2len,
662        row[2][1] / row2len,
663        row[2][2] / row2len,
664    ];
665    skew.1 /= scale.2;
666    skew.2 /= scale.2;
667
668    // At this point, the matrix (in rows) is orthonormal.
669    // Check for a coordinate system flip.  If the determinant
670    // is -1, then negate the matrix and the scaling factors.
671    if dot(row[0], cross(row[1], row[2])) < 0.0 {
672        scale.negate();
673        for i in 0..3 {
674            row[i][0] *= -1.0;
675            row[i][1] *= -1.0;
676            row[i][2] *= -1.0;
677        }
678    }
679
680    // Now, get the rotations out.
681    let mut quaternion = Quaternion(
682        0.5 * ((1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0) as f64).sqrt(),
683        0.5 * ((1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0) as f64).sqrt(),
684        0.5 * ((1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0) as f64).sqrt(),
685        0.5 * ((1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0) as f64).sqrt(),
686    );
687
688    if row[2][1] > row[1][2] {
689        quaternion.0 = -quaternion.0
690    }
691    if row[0][2] > row[2][0] {
692        quaternion.1 = -quaternion.1
693    }
694    if row[1][0] > row[0][1] {
695        quaternion.2 = -quaternion.2
696    }
697
698    Ok(MatrixDecomposed3D {
699        translate,
700        scale,
701        skew,
702        perspective,
703        quaternion,
704    })
705}
706
707/**
708 * The relevant section of the transitions specification:
709 * https://drafts.csswg.org/web-animations-1/#animation-types
710 * http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
711 * defers all of the details to the 2-D and 3-D transforms specifications.
712 * For the 2-D transforms specification (all that's relevant for us, right
713 * now), the relevant section is:
714 * https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms
715 * This, in turn, refers to the unmatrix program in Graphics Gems,
716 * available from http://graphicsgems.org/ , and in
717 * particular as the file GraphicsGems/gemsii/unmatrix.c
718 * in http://graphicsgems.org/AllGems.tar.gz
719 *
720 * The unmatrix reference is for general 3-D transform matrices (any of the
721 * 16 components can have any value).
722 *
723 * For CSS 2-D transforms, we have a 2-D matrix with the bottom row constant:
724 *
725 * [ A C E ]
726 * [ B D F ]
727 * [ 0 0 1 ]
728 *
729 * For that case, I believe the algorithm in unmatrix reduces to:
730 *
731 *  (1) If A * D - B * C == 0, the matrix is singular.  Fail.
732 *
733 *  (2) Set translation components (Tx and Ty) to the translation parts of
734 *      the matrix (E and F) and then ignore them for the rest of the time.
735 *      (For us, E and F each actually consist of three constants:  a
736 *      length, a multiplier for the width, and a multiplier for the
737 *      height.  This actually requires its own decomposition, but I'll
738 *      keep that separate.)
739 *
740 *  (3) Let the X scale (Sx) be sqrt(A^2 + B^2).  Then divide both A and B
741 *      by it.
742 *
743 *  (4) Let the XY shear (K) be A * C + B * D.  From C, subtract A times
744 *      the XY shear.  From D, subtract B times the XY shear.
745 *
746 *  (5) Let the Y scale (Sy) be sqrt(C^2 + D^2).  Divide C, D, and the XY
747 *      shear (K) by it.
748 *
749 *  (6) At this point, A * D - B * C is either 1 or -1.  If it is -1,
750 *      negate the XY shear (K), the X scale (Sx), and A, B, C, and D.
751 *      (Alternatively, we could negate the XY shear (K) and the Y scale
752 *      (Sy).)
753 *
754 *  (7) Let the rotation be R = atan2(B, A).
755 *
756 * Then the resulting decomposed transformation is:
757 *
758 *   translate(Tx, Ty) rotate(R) skewX(atan(K)) scale(Sx, Sy)
759 *
760 * An interesting result of this is that all of the simple transform
761 * functions (i.e., all functions other than matrix()), in isolation,
762 * decompose back to themselves except for:
763 *   'skewY(φ)', which is 'matrix(1, tan(φ), 0, 1, 0, 0)', which decomposes
764 *   to 'rotate(φ) skewX(φ) scale(sec(φ), cos(φ))' since (ignoring the
765 *   alternate sign possibilities that would get fixed in step 6):
766 *     In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) =
767 * sec(φ). Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) =
768 * sin(φ). In step 4, the XY shear is sin(φ). Thus, after step 4, C =
769 * -cos(φ)sin(φ) and D = 1 - sin²(φ) = cos²(φ). Thus, in step 5, the Y scale is
770 * sqrt(cos²(φ)(sin²(φ) + cos²(φ)) = cos(φ). Thus, after step 5, C = -sin(φ), D
771 * = cos(φ), and the XY shear is tan(φ). Thus, in step 6, A * D - B * C =
772 * cos²(φ) + sin²(φ) = 1. In step 7, the rotation is thus φ.
773 *
774 *   skew(θ, φ), which is matrix(1, tan(φ), tan(θ), 1, 0, 0), which decomposes
775 *   to 'rotate(φ) skewX(θ + φ) scale(sec(φ), cos(φ))' since (ignoring
776 *   the alternate sign possibilities that would get fixed in step 6):
777 *     In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) =
778 * sec(φ). Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) =
779 * sin(φ). In step 4, the XY shear is cos(φ)tan(θ) + sin(φ). Thus, after step 4,
780 *     C = tan(θ) - cos(φ)(cos(φ)tan(θ) + sin(φ)) = tan(θ)sin²(φ) - cos(φ)sin(φ)
781 *     D = 1 - sin(φ)(cos(φ)tan(θ) + sin(φ)) = cos²(φ) - sin(φ)cos(φ)tan(θ)
782 *     Thus, in step 5, the Y scale is sqrt(C² + D²) =
783 *     sqrt(tan²(θ)(sin⁴(φ) + sin²(φ)cos²(φ)) -
784 *          2 tan(θ)(sin³(φ)cos(φ) + sin(φ)cos³(φ)) +
785 *          (sin²(φ)cos²(φ) + cos⁴(φ))) =
786 *     sqrt(tan²(θ)sin²(φ) - 2 tan(θ)sin(φ)cos(φ) + cos²(φ)) =
787 *     cos(φ) - tan(θ)sin(φ) (taking the negative of the obvious solution so
788 *     we avoid flipping in step 6).
789 *     After step 5, C = -sin(φ) and D = cos(φ), and the XY shear is
790 *     (cos(φ)tan(θ) + sin(φ)) / (cos(φ) - tan(θ)sin(φ)) =
791 *     (dividing both numerator and denominator by cos(φ))
792 *     (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)) = tan(θ + φ).
793 *     (See http://en.wikipedia.org/wiki/List_of_trigonometric_identities .)
794 *     Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1.
795 *     In step 7, the rotation is thus φ.
796 *
797 *     To check this result, we can multiply things back together:
798 *
799 *     [ cos(φ) -sin(φ) ] [ 1 tan(θ + φ) ] [ sec(φ)    0   ]
800 *     [ sin(φ)  cos(φ) ] [ 0      1     ] [   0    cos(φ) ]
801 *
802 *     [ cos(φ)      cos(φ)tan(θ + φ) - sin(φ) ] [ sec(φ)    0   ]
803 *     [ sin(φ)      sin(φ)tan(θ + φ) + cos(φ) ] [   0    cos(φ) ]
804 *
805 *     but since tan(θ + φ) = (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)),
806 *     cos(φ)tan(θ + φ) - sin(φ)
807 *      = cos(φ)(tan(θ) + tan(φ)) - sin(φ) + sin(φ)tan(θ)tan(φ)
808 *      = cos(φ)tan(θ) + sin(φ) - sin(φ) + sin(φ)tan(θ)tan(φ)
809 *      = cos(φ)tan(θ) + sin(φ)tan(θ)tan(φ)
810 *      = tan(θ) (cos(φ) + sin(φ)tan(φ))
811 *      = tan(θ) sec(φ) (cos²(φ) + sin²(φ))
812 *      = tan(θ) sec(φ)
813 *     and
814 *     sin(φ)tan(θ + φ) + cos(φ)
815 *      = sin(φ)(tan(θ) + tan(φ)) + cos(φ) - cos(φ)tan(θ)tan(φ)
816 *      = tan(θ) (sin(φ) - sin(φ)) + sin(φ)tan(φ) + cos(φ)
817 *      = sec(φ) (sin²(φ) + cos²(φ))
818 *      = sec(φ)
819 *     so the above is:
820 *     [ cos(φ)  tan(θ) sec(φ) ] [ sec(φ)    0   ]
821 *     [ sin(φ)     sec(φ)     ] [   0    cos(φ) ]
822 *
823 *     [    1   tan(θ) ]
824 *     [ tan(φ)    1   ]
825 */
826
827/// Decompose a 2D matrix. This implements the above decomposition algorithm.
828fn decompose_2d_matrix(matrix: &Matrix3D) -> Result<MatrixDecomposed3D, ()> {
829    // The index is column-major, so the equivalent transform matrix is:
830    // | m11 m21  0 m41 |  =>  | m11 m21 | and translate(m41, m42)
831    // | m12 m22  0 m42 |      | m12 m22 |
832    // |   0   0  1   0 |
833    // |   0   0  0   1 |
834    let (mut m11, mut m12) = (matrix.m11, matrix.m12);
835    let (mut m21, mut m22) = (matrix.m21, matrix.m22);
836    // Check if this is a singular matrix.
837    if m11 * m22 == m12 * m21 {
838        return Err(());
839    }
840
841    let mut scale_x = (m11 * m11 + m12 * m12).sqrt();
842    m11 /= scale_x;
843    m12 /= scale_x;
844
845    let mut shear_xy = m11 * m21 + m12 * m22;
846    m21 -= m11 * shear_xy;
847    m22 -= m12 * shear_xy;
848
849    let scale_y = (m21 * m21 + m22 * m22).sqrt();
850    m21 /= scale_y;
851    m22 /= scale_y;
852    shear_xy /= scale_y;
853
854    let determinant = m11 * m22 - m12 * m21;
855    // Determinant should now be 1 or -1.
856    if 0.99 > determinant.abs() || determinant.abs() > 1.01 {
857        return Err(());
858    }
859
860    if determinant < 0. {
861        m11 = -m11;
862        m12 = -m12;
863        shear_xy = -shear_xy;
864        scale_x = -scale_x;
865    }
866
867    Ok(MatrixDecomposed3D {
868        translate: Translate3D(matrix.m41, matrix.m42, 0.),
869        scale: Scale3D(scale_x, scale_y, 1.),
870        skew: Skew(shear_xy, 0., 0.),
871        perspective: Perspective(0., 0., 0., 1.),
872        quaternion: Quaternion::from_direction_and_angle(
873            &DirectionVector::new(0., 0., 1.),
874            m12.atan2(m11) as f64,
875        ),
876    })
877}
878
879impl Animate for Matrix3D {
880    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
881        let (from, to) = if self.is_3d() || other.is_3d() {
882            (decompose_3d_matrix(*self)?, decompose_3d_matrix(*other)?)
883        } else {
884            (decompose_2d_matrix(self)?, decompose_2d_matrix(other)?)
885        };
886        // Matrices can be undecomposable due to couple reasons, e.g.,
887        // non-invertible matrices. In this case, we should report Err here,
888        // and let the caller do the fallback procedure.
889        Ok(Matrix3D::from(from.animate(&to, procedure)?))
890    }
891}
892
893impl ComputeSquaredDistance for Matrix3D {
894    #[inline]
895    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
896        let (from, to) = if self.is_3d() || other.is_3d() {
897            (decompose_3d_matrix(*self)?, decompose_3d_matrix(*other)?)
898        } else {
899            (decompose_2d_matrix(self)?, decompose_2d_matrix(other)?)
900        };
901        from.compute_squared_distance(&to)
902    }
903}
904
905// ------------------------------------
906// Animation for Transform list.
907// ------------------------------------
908fn is_matched_operation(
909    first: &ComputedTransformOperation,
910    second: &ComputedTransformOperation,
911) -> bool {
912    match (first, second) {
913        (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..))
914        | (&TransformOperation::Matrix3D(..), &TransformOperation::Matrix3D(..))
915        | (&TransformOperation::Skew(..), &TransformOperation::Skew(..))
916        | (&TransformOperation::SkewX(..), &TransformOperation::SkewX(..))
917        | (&TransformOperation::SkewY(..), &TransformOperation::SkewY(..))
918        | (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..))
919        | (&TransformOperation::Rotate3D(..), &TransformOperation::Rotate3D(..))
920        | (&TransformOperation::RotateX(..), &TransformOperation::RotateX(..))
921        | (&TransformOperation::RotateY(..), &TransformOperation::RotateY(..))
922        | (&TransformOperation::RotateZ(..), &TransformOperation::RotateZ(..))
923        | (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => true,
924        // Match functions that have the same primitive transform function
925        (a, b) if a.is_translate() && b.is_translate() => true,
926        (a, b) if a.is_scale() && b.is_scale() => true,
927        (a, b) if a.is_rotate() && b.is_rotate() => true,
928        // InterpolateMatrix and AccumulateMatrix are for mismatched transforms
929        _ => false,
930    }
931}
932
933/// <https://drafts.csswg.org/css-transforms/#interpolation-of-transforms>
934impl Animate for ComputedTransform {
935    #[inline]
936    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
937        use std::borrow::Cow;
938
939        // Addition for transforms simply means appending to the list of
940        // transform functions. This is different to how we handle the other
941        // animation procedures so we treat it separately here rather than
942        // handling it in TransformOperation.
943        if procedure == Procedure::Add {
944            let result = self.0.iter().chain(&*other.0).cloned().collect();
945            return Ok(Transform(result));
946        }
947
948        let this = Cow::Borrowed(&self.0);
949        let other = Cow::Borrowed(&other.0);
950
951        // Interpolate the common prefix
952        let mut result = this
953            .iter()
954            .zip(other.iter())
955            .take_while(|(this, other)| is_matched_operation(this, other))
956            .map(|(this, other)| this.animate(other, procedure))
957            .collect::<Result<Vec<_>, _>>()?;
958
959        // Deal with the remainders
960        let this_remainder = if this.len() > result.len() {
961            Some(&this[result.len()..])
962        } else {
963            None
964        };
965        let other_remainder = if other.len() > result.len() {
966            Some(&other[result.len()..])
967        } else {
968            None
969        };
970
971        match (this_remainder, other_remainder) {
972            // If there is a remainder from *both* lists we must have had mismatched functions.
973            // => Add the remainders to a suitable ___Matrix function.
974            (Some(this_remainder), Some(other_remainder)) => {
975                result.push(TransformOperation::animate_mismatched_transforms(
976                    this_remainder,
977                    other_remainder,
978                    procedure,
979                )?);
980            },
981            // If there is a remainder from just one list, then one list must be shorter but
982            // completely match the type of the corresponding functions in the longer list.
983            // => Interpolate the remainder with identity transforms.
984            (Some(remainder), None) | (None, Some(remainder)) => {
985                let fill_right = this_remainder.is_some();
986                result.append(
987                    &mut remainder
988                        .iter()
989                        .map(|transform| {
990                            let identity = transform.to_animated_zero().unwrap();
991
992                            match transform {
993                                TransformOperation::AccumulateMatrix { .. }
994                                | TransformOperation::InterpolateMatrix { .. } => {
995                                    let (from, to) = if fill_right {
996                                        (transform, &identity)
997                                    } else {
998                                        (&identity, transform)
999                                    };
1000
1001                                    TransformOperation::animate_mismatched_transforms(
1002                                        &[from.clone()],
1003                                        &[to.clone()],
1004                                        procedure,
1005                                    )
1006                                },
1007                                _ => {
1008                                    let (lhs, rhs) = if fill_right {
1009                                        (transform, &identity)
1010                                    } else {
1011                                        (&identity, transform)
1012                                    };
1013                                    lhs.animate(rhs, procedure)
1014                                },
1015                            }
1016                        })
1017                        .collect::<Result<Vec<_>, _>>()?,
1018                );
1019            },
1020            (None, None) => {},
1021        }
1022
1023        Ok(Transform(result.into()))
1024    }
1025}
1026
1027impl ComputeSquaredDistance for ComputedTransform {
1028    #[inline]
1029    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1030        let squared_dist = super::lists::with_zero::squared_distance(&self.0, &other.0);
1031
1032        // Roll back to matrix interpolation if there is any Err(()) in the
1033        // transform lists, such as mismatched transform functions.
1034        //
1035        // FIXME: Using a zero size here seems a bit sketchy but matches the
1036        // previous behavior.
1037        if squared_dist.is_err() {
1038            let rect = euclid::Rect::zero();
1039            let matrix1: Matrix3D = self.to_transform_3d_matrix(Some(&rect))?.0.into();
1040            let matrix2: Matrix3D = other.to_transform_3d_matrix(Some(&rect))?.0.into();
1041            return matrix1.compute_squared_distance(&matrix2);
1042        }
1043
1044        squared_dist
1045    }
1046}
1047
1048/// <http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms>
1049impl Animate for ComputedTransformOperation {
1050    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1051        match (self, other) {
1052            (TransformOperation::Matrix3D(this), TransformOperation::Matrix3D(other)) => Ok(
1053                TransformOperation::Matrix3D(this.animate(other, procedure)?),
1054            ),
1055            (TransformOperation::Matrix(this), TransformOperation::Matrix(other)) => {
1056                Ok(TransformOperation::Matrix(this.animate(other, procedure)?))
1057            },
1058            (TransformOperation::Skew(fx, fy), TransformOperation::Skew(tx, ty)) => Ok(
1059                TransformOperation::Skew(fx.animate(tx, procedure)?, fy.animate(ty, procedure)?),
1060            ),
1061            (TransformOperation::SkewX(f), TransformOperation::SkewX(t)) => {
1062                Ok(TransformOperation::SkewX(f.animate(t, procedure)?))
1063            },
1064            (TransformOperation::SkewY(f), TransformOperation::SkewY(t)) => {
1065                Ok(TransformOperation::SkewY(f.animate(t, procedure)?))
1066            },
1067            (
1068                TransformOperation::Translate3D(fx, fy, fz),
1069                TransformOperation::Translate3D(tx, ty, tz),
1070            ) => Ok(TransformOperation::Translate3D(
1071                fx.animate(tx, procedure)?,
1072                fy.animate(ty, procedure)?,
1073                fz.animate(tz, procedure)?,
1074            )),
1075            (TransformOperation::Translate(fx, fy), TransformOperation::Translate(tx, ty)) => {
1076                Ok(TransformOperation::Translate(
1077                    fx.animate(tx, procedure)?,
1078                    fy.animate(ty, procedure)?,
1079                ))
1080            },
1081            (TransformOperation::TranslateX(f), TransformOperation::TranslateX(t)) => {
1082                Ok(TransformOperation::TranslateX(f.animate(t, procedure)?))
1083            },
1084            (TransformOperation::TranslateY(f), TransformOperation::TranslateY(t)) => {
1085                Ok(TransformOperation::TranslateY(f.animate(t, procedure)?))
1086            },
1087            (TransformOperation::TranslateZ(f), TransformOperation::TranslateZ(t)) => {
1088                Ok(TransformOperation::TranslateZ(f.animate(t, procedure)?))
1089            },
1090            (TransformOperation::Scale3D(fx, fy, fz), TransformOperation::Scale3D(tx, ty, tz)) => {
1091                Ok(TransformOperation::Scale3D(
1092                    animate_multiplicative_factor(*fx, *tx, procedure)?,
1093                    animate_multiplicative_factor(*fy, *ty, procedure)?,
1094                    animate_multiplicative_factor(*fz, *tz, procedure)?,
1095                ))
1096            },
1097            (TransformOperation::ScaleX(f), TransformOperation::ScaleX(t)) => Ok(
1098                TransformOperation::ScaleX(animate_multiplicative_factor(*f, *t, procedure)?),
1099            ),
1100            (TransformOperation::ScaleY(f), TransformOperation::ScaleY(t)) => Ok(
1101                TransformOperation::ScaleY(animate_multiplicative_factor(*f, *t, procedure)?),
1102            ),
1103            (TransformOperation::ScaleZ(f), TransformOperation::ScaleZ(t)) => Ok(
1104                TransformOperation::ScaleZ(animate_multiplicative_factor(*f, *t, procedure)?),
1105            ),
1106            (TransformOperation::Scale(fx, fy), TransformOperation::Scale(tx, ty)) => {
1107                Ok(TransformOperation::Scale(
1108                    animate_multiplicative_factor(*fx, *tx, procedure)?,
1109                    animate_multiplicative_factor(*fy, *ty, procedure)?,
1110                ))
1111            },
1112            (
1113                &TransformOperation::Rotate3D(fx, fy, fz, fa),
1114                &TransformOperation::Rotate3D(tx, ty, tz, ta),
1115            ) => {
1116                let animated = Rotate::Rotate3D(fx, fy, fz, fa)
1117                    .animate(&Rotate::Rotate3D(tx, ty, tz, ta), procedure)?;
1118                let (fx, fy, fz, fa) = ComputedRotate::resolve(&animated);
1119                Ok(TransformOperation::Rotate3D(fx, fy, fz, fa))
1120            },
1121            (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta)) => {
1122                Ok(TransformOperation::RotateX(fa.animate(&ta, procedure)?))
1123            },
1124            (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta)) => {
1125                Ok(TransformOperation::RotateY(fa.animate(&ta, procedure)?))
1126            },
1127            (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta)) => {
1128                Ok(TransformOperation::RotateZ(fa.animate(&ta, procedure)?))
1129            },
1130            (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => {
1131                Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?))
1132            },
1133            (&TransformOperation::Rotate(fa), &TransformOperation::RotateZ(ta)) => {
1134                Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?))
1135            },
1136            (&TransformOperation::RotateZ(fa), &TransformOperation::Rotate(ta)) => {
1137                Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?))
1138            },
1139            (TransformOperation::Perspective(fd), TransformOperation::Perspective(td)) => {
1140                use crate::values::computed::CSSPixelLength;
1141                use crate::values::generics::transform::create_perspective_matrix;
1142
1143                // From https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions:
1144                //
1145                //    The transform functions matrix(), matrix3d() and
1146                //    perspective() get converted into 4x4 matrices first and
1147                //    interpolated as defined in section Interpolation of
1148                //    Matrices afterwards.
1149                //
1150                let from = create_perspective_matrix(fd.infinity_or(|l| l.px()));
1151                let to = create_perspective_matrix(td.infinity_or(|l| l.px()));
1152
1153                let interpolated = Matrix3D::from(from).animate(&Matrix3D::from(to), procedure)?;
1154
1155                let decomposed = decompose_3d_matrix(interpolated)?;
1156                let perspective_z = decomposed.perspective.2;
1157                // Clamp results outside of the -1 to 0 range so that we get perspective
1158                // function values between 1 and infinity.
1159                let used_value = if perspective_z >= 0. {
1160                    transform::PerspectiveFunction::None
1161                } else {
1162                    transform::PerspectiveFunction::Length(CSSPixelLength::new(
1163                        if perspective_z <= -1. {
1164                            1.
1165                        } else {
1166                            -1. / perspective_z
1167                        },
1168                    ))
1169                };
1170                Ok(TransformOperation::Perspective(used_value))
1171            },
1172            _ if self.is_translate() && other.is_translate() => self
1173                .to_translate_3d()
1174                .animate(&other.to_translate_3d(), procedure),
1175            _ if self.is_scale() && other.is_scale() => {
1176                self.to_scale_3d().animate(&other.to_scale_3d(), procedure)
1177            },
1178            _ if self.is_rotate() && other.is_rotate() => self
1179                .to_rotate_3d()
1180                .animate(&other.to_rotate_3d(), procedure),
1181            _ => Err(()),
1182        }
1183    }
1184}
1185
1186impl ComputedTransformOperation {
1187    /// If there are no size dependencies, we try to animate in-place, to avoid
1188    /// creating deeply nested Interpolate* operations.
1189    fn try_animate_mismatched_transforms_in_place(
1190        left: &[Self],
1191        right: &[Self],
1192        procedure: Procedure,
1193    ) -> Result<Self, ()> {
1194        let (left, _left_3d) = Transform::components_to_transform_3d_matrix(left, None)?;
1195        let (right, _right_3d) = Transform::components_to_transform_3d_matrix(right, None)?;
1196        Ok(Self::Matrix3D(
1197            Matrix3D::from(left).animate(&Matrix3D::from(right), procedure)?,
1198        ))
1199    }
1200
1201    fn animate_mismatched_transforms(
1202        left: &[Self],
1203        right: &[Self],
1204        procedure: Procedure,
1205    ) -> Result<Self, ()> {
1206        if let Ok(op) = Self::try_animate_mismatched_transforms_in_place(left, right, procedure) {
1207            return Ok(op);
1208        }
1209        let from_list = Transform(left.to_vec().into());
1210        let to_list = Transform(right.to_vec().into());
1211        Ok(match procedure {
1212            Procedure::Add => {
1213                debug_assert!(false, "Addition should've been handled earlier");
1214                return Err(());
1215            },
1216            Procedure::Interpolate { progress } => Self::InterpolateMatrix {
1217                from_list,
1218                to_list,
1219                progress: Percentage(progress as f32),
1220            },
1221            Procedure::Accumulate { count } => Self::AccumulateMatrix {
1222                from_list,
1223                to_list,
1224                count: cmp::min(count, i32::MAX as u64) as i32,
1225            },
1226        })
1227    }
1228}
1229
1230// This might not be the most useful definition of distance. It might be better, for example,
1231// to trace the distance travelled by a point as its transform is interpolated between the two
1232// lists. That, however, proves to be quite complicated so we take a simple approach for now.
1233// See https://bugzilla.mozilla.org/show_bug.cgi?id=1318591#c0.
1234impl ComputeSquaredDistance for ComputedTransformOperation {
1235    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1236        match (self, other) {
1237            (TransformOperation::Matrix3D(this), TransformOperation::Matrix3D(other)) => {
1238                this.compute_squared_distance(other)
1239            },
1240            (TransformOperation::Matrix(this), TransformOperation::Matrix(other)) => {
1241                let this: Matrix3D = (*this).into();
1242                let other: Matrix3D = (*other).into();
1243                this.compute_squared_distance(&other)
1244            },
1245            (TransformOperation::Skew(fx, fy), TransformOperation::Skew(tx, ty)) => {
1246                Ok(fx.compute_squared_distance(tx)? + fy.compute_squared_distance(ty)?)
1247            },
1248            (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t))
1249            | (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => {
1250                f.compute_squared_distance(t)
1251            },
1252            (
1253                TransformOperation::Translate3D(fx, fy, fz),
1254                TransformOperation::Translate3D(tx, ty, tz),
1255            ) => {
1256                // For translate, We don't want to require doing layout in order
1257                // to calculate the result, so drop the percentage part.
1258                //
1259                // However, dropping percentage makes us impossible to compute
1260                // the distance for the percentage-percentage case, but Gecko
1261                // uses the same formula, so it's fine for now.
1262                let basis = Length::new(0.);
1263                let fx = fx.resolve(basis).px();
1264                let fy = fy.resolve(basis).px();
1265                let tx = tx.resolve(basis).px();
1266                let ty = ty.resolve(basis).px();
1267
1268                Ok(fx.compute_squared_distance(&tx)?
1269                    + fy.compute_squared_distance(&ty)?
1270                    + fz.compute_squared_distance(tz)?)
1271            },
1272            (TransformOperation::Scale3D(fx, fy, fz), TransformOperation::Scale3D(tx, ty, tz)) => {
1273                Ok(fx.compute_squared_distance(tx)?
1274                    + fy.compute_squared_distance(ty)?
1275                    + fz.compute_squared_distance(tz)?)
1276            },
1277            (
1278                &TransformOperation::Rotate3D(fx, fy, fz, fa),
1279                &TransformOperation::Rotate3D(tx, ty, tz, ta),
1280            ) => Rotate::Rotate3D(fx, fy, fz, fa)
1281                .compute_squared_distance(&Rotate::Rotate3D(tx, ty, tz, ta)),
1282            (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta))
1283            | (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta))
1284            | (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta))
1285            | (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => {
1286                fa.compute_squared_distance(&ta)
1287            },
1288            (TransformOperation::Perspective(fd), TransformOperation::Perspective(td)) => fd
1289                .infinity_or(|l| l.px())
1290                .compute_squared_distance(&td.infinity_or(|l| l.px())),
1291            (&TransformOperation::Perspective(ref p), &TransformOperation::Matrix3D(ref m))
1292            | (&TransformOperation::Matrix3D(ref m), &TransformOperation::Perspective(ref p)) => {
1293                // FIXME(emilio): Is this right? Why interpolating this with
1294                // Perspective but not with anything else?
1295                let mut p_matrix = Matrix3D::identity();
1296                let p = p.infinity_or(|p| p.px());
1297                if p >= 0. {
1298                    p_matrix.m34 = -1. / p.max(1.);
1299                }
1300                p_matrix.compute_squared_distance(m)
1301            },
1302            // Gecko cross-interpolates amongst all translate and all scale
1303            // functions (See ToPrimitive in layout/style/StyleAnimationValue.cpp)
1304            // without falling back to InterpolateMatrix
1305            _ if self.is_translate() && other.is_translate() => self
1306                .to_translate_3d()
1307                .compute_squared_distance(&other.to_translate_3d()),
1308            _ if self.is_scale() && other.is_scale() => self
1309                .to_scale_3d()
1310                .compute_squared_distance(&other.to_scale_3d()),
1311            _ if self.is_rotate() && other.is_rotate() => self
1312                .to_rotate_3d()
1313                .compute_squared_distance(&other.to_rotate_3d()),
1314            _ => Err(()),
1315        }
1316    }
1317}
1318
1319// ------------------------------------
1320// Individual transforms.
1321// ------------------------------------
1322/// <https://drafts.csswg.org/css-transforms-2/#propdef-rotate>
1323impl ComputedRotate {
1324    fn resolve(&self) -> (Number, Number, Number, Angle) {
1325        // According to the spec:
1326        // https://drafts.csswg.org/css-transforms-2/#individual-transforms
1327        //
1328        // If the axis is unspecified, it defaults to "0 0 1"
1329        match *self {
1330            Rotate::None => (0., 0., 1., Angle::zero()),
1331            Rotate::Rotate3D(rx, ry, rz, angle) => (rx, ry, rz, angle),
1332            Rotate::Rotate(angle) => (0., 0., 1., angle),
1333        }
1334    }
1335}
1336
1337impl Animate for ComputedRotate {
1338    #[inline]
1339    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1340        use euclid::approxeq::ApproxEq;
1341        match (self, other) {
1342            (&Rotate::None, &Rotate::None) => Ok(Rotate::None),
1343            (&Rotate::Rotate3D(fx, fy, fz, fa), &Rotate::None) => {
1344                // We always normalize direction vector for rotate3d() first, so we should also
1345                // apply the same rule for rotate property. In other words, we promote none into
1346                // a 3d rotate, and normalize both direction vector first, and then do
1347                // interpolation.
1348                let (fx, fy, fz, fa) = transform::get_normalized_vector_and_angle(fx, fy, fz, fa);
1349                Ok(Rotate::Rotate3D(
1350                    fx,
1351                    fy,
1352                    fz,
1353                    fa.animate(&Angle::zero(), procedure)?,
1354                ))
1355            },
1356            (&Rotate::None, &Rotate::Rotate3D(tx, ty, tz, ta)) => {
1357                // Normalize direction vector first.
1358                let (tx, ty, tz, ta) = transform::get_normalized_vector_and_angle(tx, ty, tz, ta);
1359                Ok(Rotate::Rotate3D(
1360                    tx,
1361                    ty,
1362                    tz,
1363                    Angle::zero().animate(&ta, procedure)?,
1364                ))
1365            },
1366            (&Rotate::Rotate3D(..), _) | (_, &Rotate::Rotate3D(..)) => {
1367                // https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions
1368
1369                let (from, to) = (self.resolve(), other.resolve());
1370                // For interpolations with the primitive rotate3d(), the direction vectors of the
1371                // transform functions get normalized first.
1372                let (fx, fy, fz, fa) =
1373                    transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3);
1374                let (tx, ty, tz, ta) =
1375                    transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3);
1376
1377                // The rotation angle gets interpolated numerically and the rotation vector of the
1378                // non-zero angle is used or (0, 0, 1) if both angles are zero.
1379                //
1380                // Note: the normalization may get two different vectors because of the
1381                // floating-point precision, so we have to use approx_eq to compare two
1382                // vectors.
1383                let fv = DirectionVector::new(fx, fy, fz);
1384                let tv = DirectionVector::new(tx, ty, tz);
1385                if fa.is_zero() || ta.is_zero() || fv.approx_eq(&tv) {
1386                    let (x, y, z) = if fa.is_zero() && ta.is_zero() {
1387                        (0., 0., 1.)
1388                    } else if fa.is_zero() {
1389                        (tx, ty, tz)
1390                    } else {
1391                        // ta.is_zero() or both vectors are equal.
1392                        (fx, fy, fz)
1393                    };
1394                    return Ok(Rotate::Rotate3D(x, y, z, fa.animate(&ta, procedure)?));
1395                }
1396
1397                // Slerp algorithm doesn't work well for Procedure::Add, which makes both
1398                // |this_weight| and |other_weight| be 1.0, and this may make the cosine value of
1399                // the angle be out of the range (i.e. the 4th component of the quaternion vector).
1400                // (See Quaternion::animate() for more details about the Slerp formula.)
1401                // Therefore, if the cosine value is out of range, we get an NaN after applying
1402                // acos() on it, and so the result is invalid.
1403                // Note: This is specialized for `rotate` property. The addition of `transform`
1404                // property has been handled in `ComputedTransform::animate()` by merging two list
1405                // directly.
1406                let rq = if procedure == Procedure::Add {
1407                    // In Transform::animate(), it converts two rotations into transform matrices,
1408                    // and do matrix multiplication. This match the spec definition for the
1409                    // addition.
1410                    // https://drafts.csswg.org/css-transforms-2/#combining-transform-lists
1411                    let f = ComputedTransformOperation::Rotate3D(fx, fy, fz, fa);
1412                    let t = ComputedTransformOperation::Rotate3D(tx, ty, tz, ta);
1413                    let v =
1414                        Transform(vec![f].into()).animate(&Transform(vec![t].into()), procedure)?;
1415                    let (m, _) = v.to_transform_3d_matrix(None)?;
1416                    // Decompose the matrix and retrive the quaternion vector.
1417                    decompose_3d_matrix(Matrix3D::from(m))?.quaternion
1418                } else {
1419                    // If the normalized vectors are not equal and both rotation angles are
1420                    // non-zero the transform functions get converted into 4x4 matrices first and
1421                    // interpolated as defined in section Interpolation of Matrices afterwards.
1422                    // However, per the spec issue [1], we prefer to converting the rotate3D into
1423                    // quaternion vectors directly, and then apply Slerp algorithm.
1424                    //
1425                    // Both ways should be identical, and converting rotate3D into quaternion
1426                    // vectors directly can avoid redundant math operations, e.g. the generation of
1427                    // the equivalent matrix3D and the unnecessary decomposition parts of
1428                    // translation, scale, skew, and persepctive in the matrix3D.
1429                    //
1430                    // [1] https://github.com/w3c/csswg-drafts/issues/9278
1431                    let fq = Quaternion::from_direction_and_angle(&fv, fa.radians64());
1432                    let tq = Quaternion::from_direction_and_angle(&tv, ta.radians64());
1433                    Quaternion::animate(&fq, &tq, procedure)?
1434                };
1435
1436                let (x, y, z, angle) = transform::get_normalized_vector_and_angle(
1437                    rq.0 as f32,
1438                    rq.1 as f32,
1439                    rq.2 as f32,
1440                    // Due to floating point precision issues, the quaternion may contain values
1441                    // slightly larger out of the [-1.0, 1.0] range - Clamp to avoid NaN.
1442                    rq.3.clamp(-1.0, 1.0).acos() as f32 * 2.0,
1443                );
1444
1445                Ok(Rotate::Rotate3D(x, y, z, Angle::from_radians(angle)))
1446            },
1447            (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => {
1448                // If this is a 2D rotation, we just animate the <angle>
1449                let (from, to) = (self.resolve().3, other.resolve().3);
1450                Ok(Rotate::Rotate(from.animate(&to, procedure)?))
1451            },
1452        }
1453    }
1454}
1455
1456impl ComputeSquaredDistance for ComputedRotate {
1457    #[inline]
1458    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1459        use euclid::approxeq::ApproxEq;
1460        match (self, other) {
1461            (&Rotate::None, &Rotate::None) => Ok(SquaredDistance::from_sqrt(0.)),
1462            (&Rotate::Rotate3D(_, _, _, a), &Rotate::None)
1463            | (&Rotate::None, &Rotate::Rotate3D(_, _, _, a)) => {
1464                a.compute_squared_distance(&Angle::zero())
1465            },
1466            (&Rotate::Rotate3D(..), _) | (_, &Rotate::Rotate3D(..)) => {
1467                let (from, to) = (self.resolve(), other.resolve());
1468                let (mut fx, mut fy, mut fz, angle1) =
1469                    transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3);
1470                let (mut tx, mut ty, mut tz, angle2) =
1471                    transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3);
1472
1473                if angle1.is_zero() && angle2.is_zero() {
1474                    (fx, fy, fz) = (0., 0., 1.);
1475                    (tx, ty, tz) = (0., 0., 1.);
1476                } else if angle1.is_zero() {
1477                    (fx, fy, fz) = (tx, ty, tz);
1478                } else if angle2.is_zero() {
1479                    (tx, ty, tz) = (fx, fy, fz);
1480                }
1481
1482                let v1 = DirectionVector::new(fx, fy, fz);
1483                let v2 = DirectionVector::new(tx, ty, tz);
1484                if v1.approx_eq(&v2) {
1485                    angle1.compute_squared_distance(&angle2)
1486                } else {
1487                    let q1 = Quaternion::from_direction_and_angle(&v1, angle1.radians64());
1488                    let q2 = Quaternion::from_direction_and_angle(&v2, angle2.radians64());
1489                    q1.compute_squared_distance(&q2)
1490                }
1491            },
1492            (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => self
1493                .resolve()
1494                .3
1495                .compute_squared_distance(&other.resolve().3),
1496        }
1497    }
1498}
1499
1500/// <https://drafts.csswg.org/css-transforms-2/#propdef-translate>
1501impl ComputedTranslate {
1502    fn resolve(&self) -> (LengthPercentage, LengthPercentage, Length) {
1503        // According to the spec:
1504        // https://drafts.csswg.org/css-transforms-2/#individual-transforms
1505        //
1506        // Unspecified translations default to 0px
1507        match *self {
1508            Translate::None => (
1509                LengthPercentage::zero(),
1510                LengthPercentage::zero(),
1511                Length::zero(),
1512            ),
1513            Translate::Translate(ref tx, ref ty, ref tz) => (tx.clone(), ty.clone(), *tz),
1514        }
1515    }
1516}
1517
1518impl Animate for ComputedTranslate {
1519    #[inline]
1520    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1521        match (self, other) {
1522            (&Translate::None, &Translate::None) => Ok(Translate::None),
1523            (&Translate::Translate(..), _) | (_, &Translate::Translate(..)) => {
1524                let (from, to) = (self.resolve(), other.resolve());
1525                Ok(Translate::Translate(
1526                    from.0.animate(&to.0, procedure)?,
1527                    from.1.animate(&to.1, procedure)?,
1528                    from.2.animate(&to.2, procedure)?,
1529                ))
1530            },
1531        }
1532    }
1533}
1534
1535impl ComputeSquaredDistance for ComputedTranslate {
1536    #[inline]
1537    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1538        let (from, to) = (self.resolve(), other.resolve());
1539        Ok(from.0.compute_squared_distance(&to.0)?
1540            + from.1.compute_squared_distance(&to.1)?
1541            + from.2.compute_squared_distance(&to.2)?)
1542    }
1543}
1544
1545/// <https://drafts.csswg.org/css-transforms-2/#propdef-scale>
1546impl ComputedScale {
1547    fn resolve(&self) -> (Number, Number, Number) {
1548        // According to the spec:
1549        // https://drafts.csswg.org/css-transforms-2/#individual-transforms
1550        //
1551        // Unspecified scales default to 1
1552        match *self {
1553            Scale::None => (1.0, 1.0, 1.0),
1554            Scale::Scale(sx, sy, sz) => (sx, sy, sz),
1555        }
1556    }
1557}
1558
1559impl Animate for ComputedScale {
1560    #[inline]
1561    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1562        match (self, other) {
1563            (&Scale::None, &Scale::None) => Ok(Scale::None),
1564            (&Scale::Scale(..), _) | (_, &Scale::Scale(..)) => {
1565                let (from, to) = (self.resolve(), other.resolve());
1566                // For transform lists, we add by appending to the list of
1567                // transform functions. However, ComputedScale cannot be
1568                // simply concatenated, so we have to calculate the additive
1569                // result here.
1570                if procedure == Procedure::Add {
1571                    // scale(x1,y1,z1)*scale(x2,y2,z2) = scale(x1*x2, y1*y2, z1*z2)
1572                    return Ok(Scale::Scale(from.0 * to.0, from.1 * to.1, from.2 * to.2));
1573                }
1574                Ok(Scale::Scale(
1575                    animate_multiplicative_factor(from.0, to.0, procedure)?,
1576                    animate_multiplicative_factor(from.1, to.1, procedure)?,
1577                    animate_multiplicative_factor(from.2, to.2, procedure)?,
1578                ))
1579            },
1580        }
1581    }
1582}
1583
1584impl ComputeSquaredDistance for ComputedScale {
1585    #[inline]
1586    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1587        let (from, to) = (self.resolve(), other.resolve());
1588        Ok(from.0.compute_squared_distance(&to.0)?
1589            + from.1.compute_squared_distance(&to.1)?
1590            + from.2.compute_squared_distance(&to.2)?)
1591    }
1592}