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