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