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 
9 use super::animate_multiplicative_factor;
10 use super::{Animate, Procedure, ToAnimatedZero};
11 use crate::properties::animated_properties::ListAnimation;
12 use crate::values::computed::transform::Rotate as ComputedRotate;
13 use crate::values::computed::transform::Scale as ComputedScale;
14 use crate::values::computed::transform::Transform as ComputedTransform;
15 use crate::values::computed::transform::TransformOperation as ComputedTransformOperation;
16 use crate::values::computed::transform::Translate as ComputedTranslate;
17 use crate::values::computed::transform::{DirectionVector, Matrix, Matrix3D};
18 use crate::values::computed::Angle;
19 use crate::values::computed::{Length, LengthPercentage};
20 use crate::values::computed::{Number, Percentage};
21 use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
22 use crate::values::generics::transform::{self, Transform, TransformOperation};
23 use crate::values::generics::transform::{Rotate, Scale, Translate};
24 use crate::values::CSSFloat;
25 use crate::Zero;
26 use std::cmp;
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.
39 pub struct InnerMatrix2D {
40     pub m11: CSSFloat,
41     pub m12: CSSFloat,
42     pub m21: CSSFloat,
43     pub m22: CSSFloat,
44 }
45 
46 impl Animate for InnerMatrix2D {
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>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)]
60 pub struct Translate2D(f32, f32);
61 
62 /// A 2d scale function.
63 #[derive(Clone, ComputeSquaredDistance, Copy, Debug)]
64 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
65 pub struct Scale2D(f32, f32);
66 
67 impl Animate for Scale2D {
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>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))]
79 pub 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 
90 impl Animate for MatrixDecomposed2D {
91     /// <https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-2d-matrix-values>
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>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 
135 impl ComputeSquaredDistance for MatrixDecomposed2D {
136     #[inline]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>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 
149 impl From<Matrix3D> for MatrixDecomposed2D {
150     /// Decompose a 2D matrix.
151     /// <https://drafts.csswg.org/css-transforms/#decomposing-a-2d-matrix>
from(matrix: Matrix3D) -> MatrixDecomposed2D152     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 
217 impl From<MatrixDecomposed2D> for Matrix3D {
218     /// Recompose a 2D matrix.
219     /// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-2d-matrix>
from(decomposed: MatrixDecomposed2D) -> Matrix3D220     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 
254 impl Animate for Matrix {
255     #[cfg(feature = "servo")]
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>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         Ok(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
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>267     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
268         let from = decompose_2d_matrix(&(*self).into());
269         let to = decompose_2d_matrix(&(*other).into());
270         match (from, to) {
271             (Ok(from), Ok(to)) => Matrix3D::from(from.animate(&to, procedure)?).into_2d(),
272             // Matrices can be undecomposable due to couple reasons, e.g.,
273             // non-invertible matrices. In this case, we should report Err here,
274             // and let the caller do the fallback procedure.
275             _ => Err(()),
276         }
277     }
278 }
279 
280 /// A 3d translation.
281 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
282 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)]
283 pub struct Translate3D(pub f32, pub f32, pub f32);
284 
285 /// A 3d scale function.
286 #[derive(Clone, ComputeSquaredDistance, Copy, Debug)]
287 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
288 pub struct Scale3D(pub f32, pub f32, pub f32);
289 
290 impl Scale3D {
291     /// Negate self.
negate(&mut self)292     fn negate(&mut self) {
293         self.0 *= -1.0;
294         self.1 *= -1.0;
295         self.2 *= -1.0;
296     }
297 }
298 
299 impl Animate for Scale3D {
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>300     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
301         Ok(Scale3D(
302             animate_multiplicative_factor(self.0, other.0, procedure)?,
303             animate_multiplicative_factor(self.1, other.1, procedure)?,
304             animate_multiplicative_factor(self.2, other.2, procedure)?,
305         ))
306     }
307 }
308 
309 /// A 3d skew function.
310 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
311 #[derive(Animate, Clone, Copy, Debug)]
312 pub struct Skew(f32, f32, f32);
313 
314 impl ComputeSquaredDistance for Skew {
315     // We have to use atan() to convert the skew factors into skew angles, so implement
316     // ComputeSquaredDistance manually.
317     #[inline]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>318     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
319         Ok(self.0.atan().compute_squared_distance(&other.0.atan())? +
320             self.1.atan().compute_squared_distance(&other.1.atan())? +
321             self.2.atan().compute_squared_distance(&other.2.atan())?)
322     }
323 }
324 
325 /// A 3d perspective transformation.
326 #[derive(Clone, ComputeSquaredDistance, Copy, Debug)]
327 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
328 pub struct Perspective(pub f32, pub f32, pub f32, pub f32);
329 
330 impl Animate for Perspective {
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>331     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
332         Ok(Perspective(
333             self.0.animate(&other.0, procedure)?,
334             self.1.animate(&other.1, procedure)?,
335             self.2.animate(&other.2, procedure)?,
336             animate_multiplicative_factor(self.3, other.3, procedure)?,
337         ))
338     }
339 }
340 
341 /// A quaternion used to represent a rotation.
342 #[derive(Clone, Copy, Debug)]
343 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
344 pub struct Quaternion(f64, f64, f64, f64);
345 
346 impl Quaternion {
347     /// Return a quaternion from a unit direction vector and angle (unit: radian).
348     #[inline]
from_direction_and_angle(vector: &DirectionVector, angle: f64) -> Self349     fn from_direction_and_angle(vector: &DirectionVector, angle: f64) -> Self {
350         debug_assert!(
351             (vector.length() - 1.).abs() < 0.0001,
352             "Only accept an unit direction vector to create a quaternion"
353         );
354         // Reference:
355         // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation
356         //
357         // if the direction axis is (x, y, z) = xi + yj + zk,
358         // and the angle is |theta|, this formula can be done using
359         // an extension of Euler's formula:
360         //   q = cos(theta/2) + (xi + yj + zk)(sin(theta/2))
361         //     = cos(theta/2) +
362         //       x*sin(theta/2)i + y*sin(theta/2)j + z*sin(theta/2)k
363         Quaternion(
364             vector.x as f64 * (angle / 2.).sin(),
365             vector.y as f64 * (angle / 2.).sin(),
366             vector.z as f64 * (angle / 2.).sin(),
367             (angle / 2.).cos(),
368         )
369     }
370 
371     /// Calculate the dot product.
372     #[inline]
dot(&self, other: &Self) -> f64373     fn dot(&self, other: &Self) -> f64 {
374         self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3
375     }
376 
377     /// Return the scaled quaternion by a factor.
378     #[inline]
scale(&self, factor: f64) -> Self379     fn scale(&self, factor: f64) -> Self {
380         Quaternion(
381             self.0 * factor,
382             self.1 * factor,
383             self.2 * factor,
384             self.3 * factor,
385         )
386     }
387 }
388 
389 impl Animate for Quaternion {
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>390     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
391         use std::f64;
392 
393         let (this_weight, other_weight) = procedure.weights();
394         debug_assert!(
395             // Doule EPSILON since both this_weight and other_weght have calculation errors
396             // which are approximately equal to EPSILON.
397             (this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON * 2.0 ||
398                 other_weight == 1.0f64 ||
399                 other_weight == 0.0f64,
400             "animate should only be used for interpolating or accumulating transforms"
401         );
402 
403         // We take a specialized code path for accumulation (where other_weight
404         // is 1).
405         if let Procedure::Accumulate { .. } = procedure {
406             debug_assert_eq!(other_weight, 1.0);
407             if this_weight == 0.0 {
408                 return Ok(*other);
409             }
410 
411             let clamped_w = self.3.min(1.0).max(-1.0);
412 
413             // Determine the scale factor.
414             let mut theta = clamped_w.acos();
415             let mut scale = if theta == 0.0 { 0.0 } else { 1.0 / theta.sin() };
416             theta *= this_weight;
417             scale *= theta.sin();
418 
419             // Scale the self matrix by this_weight.
420             let mut scaled_self = *self;
421             scaled_self.0 *= scale;
422             scaled_self.1 *= scale;
423             scaled_self.2 *= scale;
424             scaled_self.3 = theta.cos();
425 
426             // Multiply scaled-self by other.
427             let a = &scaled_self;
428             let b = other;
429             return Ok(Quaternion(
430                 a.3 * b.0 + a.0 * b.3 + a.1 * b.2 - a.2 * b.1,
431                 a.3 * b.1 - a.0 * b.2 + a.1 * b.3 + a.2 * b.0,
432                 a.3 * b.2 + a.0 * b.1 - a.1 * b.0 + a.2 * b.3,
433                 a.3 * b.3 - a.0 * b.0 - a.1 * b.1 - a.2 * b.2,
434             ));
435         }
436 
437         // Straight from gfxQuaternion::Slerp.
438         //
439         // Dot product, clamped between -1 and 1.
440         let dot = (self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3)
441             .min(1.0)
442             .max(-1.0);
443 
444         if dot.abs() == 1.0 {
445             return Ok(*self);
446         }
447 
448         let theta = dot.acos();
449         let rsintheta = 1.0 / (1.0 - dot * dot).sqrt();
450 
451         let right_weight = (other_weight * theta).sin() * rsintheta;
452         let left_weight = (other_weight * theta).cos() - dot * right_weight;
453 
454         let left = self.scale(left_weight);
455         let right = other.scale(right_weight);
456 
457         Ok(Quaternion(
458             left.0 + right.0,
459             left.1 + right.1,
460             left.2 + right.2,
461             left.3 + right.3,
462         ))
463     }
464 }
465 
466 impl ComputeSquaredDistance for Quaternion {
467     #[inline]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>468     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
469         // Use quaternion vectors to get the angle difference. Both q1 and q2 are unit vectors,
470         // so we can get their angle difference by:
471         // cos(theta/2) = (q1 dot q2) / (|q1| * |q2|) = q1 dot q2.
472         let distance = self.dot(other).max(-1.0).min(1.0).acos() * 2.0;
473         Ok(SquaredDistance::from_sqrt(distance))
474     }
475 }
476 
477 /// A decomposed 3d matrix.
478 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)]
479 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
480 pub struct MatrixDecomposed3D {
481     /// A translation function.
482     pub translate: Translate3D,
483     /// A scale function.
484     pub scale: Scale3D,
485     /// The skew component of the transformation.
486     pub skew: Skew,
487     /// The perspective component of the transformation.
488     pub perspective: Perspective,
489     /// The quaternion used to represent the rotation.
490     pub quaternion: Quaternion,
491 }
492 
493 impl From<MatrixDecomposed3D> for Matrix3D {
494     /// Recompose a 3D matrix.
495     /// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-3d-matrix>
from(decomposed: MatrixDecomposed3D) -> Matrix3D496     fn from(decomposed: MatrixDecomposed3D) -> Matrix3D {
497         let mut matrix = Matrix3D::identity();
498 
499         // Apply perspective
500         matrix.set_perspective(&decomposed.perspective);
501 
502         // Apply translation
503         matrix.apply_translate(&decomposed.translate);
504 
505         // Apply rotation
506         {
507             let x = decomposed.quaternion.0;
508             let y = decomposed.quaternion.1;
509             let z = decomposed.quaternion.2;
510             let w = decomposed.quaternion.3;
511 
512             // Construct a composite rotation matrix from the quaternion values
513             // rotationMatrix is a identity 4x4 matrix initially
514             let mut rotation_matrix = Matrix3D::identity();
515             rotation_matrix.m11 = 1.0 - 2.0 * (y * y + z * z) as f32;
516             rotation_matrix.m12 = 2.0 * (x * y + z * w) as f32;
517             rotation_matrix.m13 = 2.0 * (x * z - y * w) as f32;
518             rotation_matrix.m21 = 2.0 * (x * y - z * w) as f32;
519             rotation_matrix.m22 = 1.0 - 2.0 * (x * x + z * z) as f32;
520             rotation_matrix.m23 = 2.0 * (y * z + x * w) as f32;
521             rotation_matrix.m31 = 2.0 * (x * z + y * w) as f32;
522             rotation_matrix.m32 = 2.0 * (y * z - x * w) as f32;
523             rotation_matrix.m33 = 1.0 - 2.0 * (x * x + y * y) as f32;
524 
525             matrix = rotation_matrix.multiply(&matrix);
526         }
527 
528         // Apply skew
529         {
530             let mut temp = Matrix3D::identity();
531             if decomposed.skew.2 != 0.0 {
532                 temp.m32 = decomposed.skew.2;
533                 matrix = temp.multiply(&matrix);
534                 temp.m32 = 0.0;
535             }
536 
537             if decomposed.skew.1 != 0.0 {
538                 temp.m31 = decomposed.skew.1;
539                 matrix = temp.multiply(&matrix);
540                 temp.m31 = 0.0;
541             }
542 
543             if decomposed.skew.0 != 0.0 {
544                 temp.m21 = decomposed.skew.0;
545                 matrix = temp.multiply(&matrix);
546             }
547         }
548 
549         // Apply scale
550         matrix.apply_scale(&decomposed.scale);
551 
552         matrix
553     }
554 }
555 
556 /// Decompose a 3D matrix.
557 /// https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix
558 /// http://www.realtimerendering.com/resources/GraphicsGems/gemsii/unmatrix.c
decompose_3d_matrix(mut matrix: Matrix3D) -> Result<MatrixDecomposed3D, ()>559 fn decompose_3d_matrix(mut matrix: Matrix3D) -> Result<MatrixDecomposed3D, ()> {
560     // Combine 2 point.
561     let combine = |a: [f32; 3], b: [f32; 3], ascl: f32, bscl: f32| {
562         [
563             (ascl * a[0]) + (bscl * b[0]),
564             (ascl * a[1]) + (bscl * b[1]),
565             (ascl * a[2]) + (bscl * b[2]),
566         ]
567     };
568     // Dot product.
569     let dot = |a: [f32; 3], b: [f32; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
570     // Cross product.
571     let cross = |row1: [f32; 3], row2: [f32; 3]| {
572         [
573             row1[1] * row2[2] - row1[2] * row2[1],
574             row1[2] * row2[0] - row1[0] * row2[2],
575             row1[0] * row2[1] - row1[1] * row2[0],
576         ]
577     };
578 
579     if matrix.m44 == 0.0 {
580         return Err(());
581     }
582 
583     let scaling_factor = matrix.m44;
584 
585     // Normalize the matrix.
586     matrix.scale_by_factor(1.0 / scaling_factor);
587 
588     // perspective_matrix is used to solve for perspective, but it also provides
589     // an easy way to test for singularity of the upper 3x3 component.
590     let mut perspective_matrix = matrix;
591 
592     perspective_matrix.m14 = 0.0;
593     perspective_matrix.m24 = 0.0;
594     perspective_matrix.m34 = 0.0;
595     perspective_matrix.m44 = 1.0;
596 
597     if perspective_matrix.determinant() == 0.0 {
598         return Err(());
599     }
600 
601     // First, isolate perspective.
602     let perspective = if matrix.m14 != 0.0 || matrix.m24 != 0.0 || matrix.m34 != 0.0 {
603         let right_hand_side: [f32; 4] = [matrix.m14, matrix.m24, matrix.m34, matrix.m44];
604 
605         perspective_matrix = perspective_matrix.inverse().unwrap().transpose();
606         let perspective = perspective_matrix.pre_mul_point4(&right_hand_side);
607         // NOTE(emilio): Even though the reference algorithm clears the
608         // fourth column here (matrix.m14..matrix.m44), they're not used below
609         // so it's not really needed.
610         Perspective(
611             perspective[0],
612             perspective[1],
613             perspective[2],
614             perspective[3],
615         )
616     } else {
617         Perspective(0.0, 0.0, 0.0, 1.0)
618     };
619 
620     // Next take care of translation (easy).
621     let translate = Translate3D(matrix.m41, matrix.m42, matrix.m43);
622 
623     // Now get scale and shear. 'row' is a 3 element array of 3 component vectors
624     let mut row = matrix.get_matrix_3x3_part();
625 
626     // Compute X scale factor and normalize first row.
627     let row0len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt();
628     let mut scale = Scale3D(row0len, 0.0, 0.0);
629     row[0] = [
630         row[0][0] / row0len,
631         row[0][1] / row0len,
632         row[0][2] / row0len,
633     ];
634 
635     // Compute XY shear factor and make 2nd row orthogonal to 1st.
636     let mut skew = Skew(dot(row[0], row[1]), 0.0, 0.0);
637     row[1] = combine(row[1], row[0], 1.0, -skew.0);
638 
639     // Now, compute Y scale and normalize 2nd row.
640     let row1len = (row[1][0] * row[1][0] + row[1][1] * row[1][1] + row[1][2] * row[1][2]).sqrt();
641     scale.1 = row1len;
642     row[1] = [
643         row[1][0] / row1len,
644         row[1][1] / row1len,
645         row[1][2] / row1len,
646     ];
647     skew.0 /= scale.1;
648 
649     // Compute XZ and YZ shears, orthogonalize 3rd row
650     skew.1 = dot(row[0], row[2]);
651     row[2] = combine(row[2], row[0], 1.0, -skew.1);
652     skew.2 = dot(row[1], row[2]);
653     row[2] = combine(row[2], row[1], 1.0, -skew.2);
654 
655     // Next, get Z scale and normalize 3rd row.
656     let row2len = (row[2][0] * row[2][0] + row[2][1] * row[2][1] + row[2][2] * row[2][2]).sqrt();
657     scale.2 = row2len;
658     row[2] = [
659         row[2][0] / row2len,
660         row[2][1] / row2len,
661         row[2][2] / row2len,
662     ];
663     skew.1 /= scale.2;
664     skew.2 /= scale.2;
665 
666     // At this point, the matrix (in rows) is orthonormal.
667     // Check for a coordinate system flip.  If the determinant
668     // is -1, then negate the matrix and the scaling factors.
669     if dot(row[0], cross(row[1], row[2])) < 0.0 {
670         scale.negate();
671         for i in 0..3 {
672             row[i][0] *= -1.0;
673             row[i][1] *= -1.0;
674             row[i][2] *= -1.0;
675         }
676     }
677 
678     // Now, get the rotations out.
679     let mut quaternion = Quaternion(
680         0.5 * ((1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0) as f64).sqrt(),
681         0.5 * ((1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0) as f64).sqrt(),
682         0.5 * ((1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0) as f64).sqrt(),
683         0.5 * ((1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0) as f64).sqrt(),
684     );
685 
686     if row[2][1] > row[1][2] {
687         quaternion.0 = -quaternion.0
688     }
689     if row[0][2] > row[2][0] {
690         quaternion.1 = -quaternion.1
691     }
692     if row[1][0] > row[0][1] {
693         quaternion.2 = -quaternion.2
694     }
695 
696     Ok(MatrixDecomposed3D {
697         translate,
698         scale,
699         skew,
700         perspective,
701         quaternion,
702     })
703 }
704 
705 /// Decompose a 2D matrix for Gecko.
706 // Use the algorithm from nsStyleTransformMatrix::Decompose2DMatrix() in Gecko.
707 #[cfg(feature = "gecko")]
decompose_2d_matrix(matrix: &Matrix3D) -> Result<MatrixDecomposed3D, ()>708 fn decompose_2d_matrix(matrix: &Matrix3D) -> Result<MatrixDecomposed3D, ()> {
709     // The index is column-major, so the equivalent transform matrix is:
710     // | m11 m21  0 m41 |  =>  | m11 m21 | and translate(m41, m42)
711     // | m12 m22  0 m42 |      | m12 m22 |
712     // |   0   0  1   0 |
713     // |   0   0  0   1 |
714     let (mut m11, mut m12) = (matrix.m11, matrix.m12);
715     let (mut m21, mut m22) = (matrix.m21, matrix.m22);
716     // Check if this is a singular matrix.
717     if m11 * m22 == m12 * m21 {
718         return Err(());
719     }
720 
721     let mut scale_x = (m11 * m11 + m12 * m12).sqrt();
722     m11 /= scale_x;
723     m12 /= scale_x;
724 
725     let mut shear_xy = m11 * m21 + m12 * m22;
726     m21 -= m11 * shear_xy;
727     m22 -= m12 * shear_xy;
728 
729     let scale_y = (m21 * m21 + m22 * m22).sqrt();
730     m21 /= scale_y;
731     m22 /= scale_y;
732     shear_xy /= scale_y;
733 
734     let determinant = m11 * m22 - m12 * m21;
735     // Determinant should now be 1 or -1.
736     if 0.99 > determinant.abs() || determinant.abs() > 1.01 {
737         return Err(());
738     }
739 
740     if determinant < 0. {
741         m11 = -m11;
742         m12 = -m12;
743         shear_xy = -shear_xy;
744         scale_x = -scale_x;
745     }
746 
747     Ok(MatrixDecomposed3D {
748         translate: Translate3D(matrix.m41, matrix.m42, 0.),
749         scale: Scale3D(scale_x, scale_y, 1.),
750         skew: Skew(shear_xy, 0., 0.),
751         perspective: Perspective(0., 0., 0., 1.),
752         quaternion: Quaternion::from_direction_and_angle(
753             &DirectionVector::new(0., 0., 1.),
754             m12.atan2(m11) as f64,
755         ),
756     })
757 }
758 
759 impl Animate for Matrix3D {
760     #[cfg(feature = "servo")]
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>761     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
762         if self.is_3d() || other.is_3d() {
763             let decomposed_from = decompose_3d_matrix(*self);
764             let decomposed_to = decompose_3d_matrix(*other);
765             match (decomposed_from, decomposed_to) {
766                 (Ok(this), Ok(other)) => Ok(Matrix3D::from(this.animate(&other, procedure)?)),
767                 // Matrices can be undecomposable due to couple reasons, e.g.,
768                 // non-invertible matrices. In this case, we should report Err
769                 // here, and let the caller do the fallback procedure.
770                 _ => Err(()),
771             }
772         } else {
773             let this = MatrixDecomposed2D::from(*self);
774             let other = MatrixDecomposed2D::from(*other);
775             Ok(Matrix3D::from(this.animate(&other, procedure)?))
776         }
777     }
778 
779     #[cfg(feature = "gecko")]
780     // Gecko doesn't exactly follow the spec here; we use a different procedure
781     // to match it
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>782     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
783         let (from, to) = if self.is_3d() || other.is_3d() {
784             (decompose_3d_matrix(*self), decompose_3d_matrix(*other))
785         } else {
786             (decompose_2d_matrix(self), decompose_2d_matrix(other))
787         };
788         match (from, to) {
789             (Ok(from), Ok(to)) => Ok(Matrix3D::from(from.animate(&to, procedure)?)),
790             // Matrices can be undecomposable due to couple reasons, e.g.,
791             // non-invertible matrices. In this case, we should report Err here,
792             // and let the caller do the fallback procedure.
793             _ => Err(()),
794         }
795     }
796 }
797 
798 impl ComputeSquaredDistance for Matrix3D {
799     #[inline]
800     #[cfg(feature = "servo")]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>801     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
802         if self.is_3d() || other.is_3d() {
803             let from = decompose_3d_matrix(*self)?;
804             let to = decompose_3d_matrix(*other)?;
805             from.compute_squared_distance(&to)
806         } else {
807             let from = MatrixDecomposed2D::from(*self);
808             let to = MatrixDecomposed2D::from(*other);
809             from.compute_squared_distance(&to)
810         }
811     }
812 
813     #[inline]
814     #[cfg(feature = "gecko")]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>815     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
816         let (from, to) = if self.is_3d() || other.is_3d() {
817             (decompose_3d_matrix(*self)?, decompose_3d_matrix(*other)?)
818         } else {
819             (decompose_2d_matrix(self)?, decompose_2d_matrix(other)?)
820         };
821         from.compute_squared_distance(&to)
822     }
823 }
824 
825 // ------------------------------------
826 // Animation for Transform list.
827 // ------------------------------------
is_matched_operation( first: &ComputedTransformOperation, second: &ComputedTransformOperation, ) -> bool828 fn is_matched_operation(
829     first: &ComputedTransformOperation,
830     second: &ComputedTransformOperation,
831 ) -> bool {
832     match (first, second) {
833         (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) |
834         (&TransformOperation::Matrix3D(..), &TransformOperation::Matrix3D(..)) |
835         (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) |
836         (&TransformOperation::SkewX(..), &TransformOperation::SkewX(..)) |
837         (&TransformOperation::SkewY(..), &TransformOperation::SkewY(..)) |
838         (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) |
839         (&TransformOperation::Rotate3D(..), &TransformOperation::Rotate3D(..)) |
840         (&TransformOperation::RotateX(..), &TransformOperation::RotateX(..)) |
841         (&TransformOperation::RotateY(..), &TransformOperation::RotateY(..)) |
842         (&TransformOperation::RotateZ(..), &TransformOperation::RotateZ(..)) |
843         (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => true,
844         // Match functions that have the same primitive transform function
845         (a, b) if a.is_translate() && b.is_translate() => true,
846         (a, b) if a.is_scale() && b.is_scale() => true,
847         (a, b) if a.is_rotate() && b.is_rotate() => true,
848         // InterpolateMatrix and AccumulateMatrix are for mismatched transforms
849         _ => false,
850     }
851 }
852 
853 /// <https://drafts.csswg.org/css-transforms/#interpolation-of-transforms>
854 impl Animate for ComputedTransform {
855     #[inline]
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>856     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
857         use std::borrow::Cow;
858 
859         // Addition for transforms simply means appending to the list of
860         // transform functions. This is different to how we handle the other
861         // animation procedures so we treat it separately here rather than
862         // handling it in TransformOperation.
863         if procedure == Procedure::Add {
864             let result = self.0.iter().chain(&*other.0).cloned().collect();
865             return Ok(Transform(result));
866         }
867 
868         let this = Cow::Borrowed(&self.0);
869         let other = Cow::Borrowed(&other.0);
870 
871         // Interpolate the common prefix
872         let mut result = this
873             .iter()
874             .zip(other.iter())
875             .take_while(|(this, other)| is_matched_operation(this, other))
876             .map(|(this, other)| this.animate(other, procedure))
877             .collect::<Result<Vec<_>, _>>()?;
878 
879         // Deal with the remainders
880         let this_remainder = if this.len() > result.len() {
881             Some(&this[result.len()..])
882         } else {
883             None
884         };
885         let other_remainder = if other.len() > result.len() {
886             Some(&other[result.len()..])
887         } else {
888             None
889         };
890 
891         match (this_remainder, other_remainder) {
892             // If there is a remainder from *both* lists we must have had mismatched functions.
893             // => Add the remainders to a suitable ___Matrix function.
894             (Some(this_remainder), Some(other_remainder)) => match procedure {
895                 Procedure::Add => {
896                     debug_assert!(false, "Should have already dealt with add by the point");
897                     return Err(());
898                 },
899                 Procedure::Interpolate { progress } => {
900                     result.push(TransformOperation::InterpolateMatrix {
901                         from_list: Transform(this_remainder.to_vec().into()),
902                         to_list: Transform(other_remainder.to_vec().into()),
903                         progress: Percentage(progress as f32),
904                     });
905                 },
906                 Procedure::Accumulate { count } => {
907                     result.push(TransformOperation::AccumulateMatrix {
908                         from_list: Transform(this_remainder.to_vec().into()),
909                         to_list: Transform(other_remainder.to_vec().into()),
910                         count: cmp::min(count, i32::max_value() as u64) as i32,
911                     });
912                 },
913             },
914             // If there is a remainder from just one list, then one list must be shorter but
915             // completely match the type of the corresponding functions in the longer list.
916             // => Interpolate the remainder with identity transforms.
917             (Some(remainder), None) | (None, Some(remainder)) => {
918                 let fill_right = this_remainder.is_some();
919                 result.append(
920                     &mut remainder
921                         .iter()
922                         .map(|transform| {
923                             let identity = transform.to_animated_zero().unwrap();
924 
925                             match transform {
926                                 // We can't interpolate/accumulate ___Matrix types directly with a
927                                 // matrix. Instead we need to wrap it in another ___Matrix type.
928                                 TransformOperation::AccumulateMatrix { .. } |
929                                 TransformOperation::InterpolateMatrix { .. } => {
930                                     let transform_list = Transform(vec![transform.clone()].into());
931                                     let identity_list = Transform(vec![identity].into());
932                                     let (from_list, to_list) = if fill_right {
933                                         (transform_list, identity_list)
934                                     } else {
935                                         (identity_list, transform_list)
936                                     };
937 
938                                     match procedure {
939                                         Procedure::Add => Err(()),
940                                         Procedure::Interpolate { progress } => {
941                                             Ok(TransformOperation::InterpolateMatrix {
942                                                 from_list,
943                                                 to_list,
944                                                 progress: Percentage(progress as f32),
945                                             })
946                                         },
947                                         Procedure::Accumulate { count } => {
948                                             Ok(TransformOperation::AccumulateMatrix {
949                                                 from_list,
950                                                 to_list,
951                                                 count: cmp::min(count, i32::max_value() as u64)
952                                                     as i32,
953                                             })
954                                         },
955                                     }
956                                 },
957                                 _ => {
958                                     let (lhs, rhs) = if fill_right {
959                                         (transform, &identity)
960                                     } else {
961                                         (&identity, transform)
962                                     };
963                                     lhs.animate(rhs, procedure)
964                                 },
965                             }
966                         })
967                         .collect::<Result<Vec<_>, _>>()?,
968                 );
969             },
970             (None, None) => {},
971         }
972 
973         Ok(Transform(result.into()))
974     }
975 }
976 
977 impl ComputeSquaredDistance for ComputedTransform {
978     #[inline]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>979     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
980         let squared_dist = self.0.squared_distance_with_zero(&other.0);
981 
982         // Roll back to matrix interpolation if there is any Err(()) in the
983         // transform lists, such as mismatched transform functions.
984         if squared_dist.is_err() {
985             let matrix1: Matrix3D = self.to_transform_3d_matrix(None)?.0.into();
986             let matrix2: Matrix3D = other.to_transform_3d_matrix(None)?.0.into();
987             return matrix1.compute_squared_distance(&matrix2);
988         }
989 
990         squared_dist
991     }
992 }
993 
994 /// <http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms>
995 impl Animate for ComputedTransformOperation {
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>996     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
997         match (self, other) {
998             (&TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other)) => {
999                 Ok(TransformOperation::Matrix3D(
1000                     this.animate(other, procedure)?,
1001                 ))
1002             },
1003             (&TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other)) => {
1004                 Ok(TransformOperation::Matrix(this.animate(other, procedure)?))
1005             },
1006             (
1007                 &TransformOperation::Skew(ref fx, ref fy),
1008                 &TransformOperation::Skew(ref tx, ref ty),
1009             ) => Ok(TransformOperation::Skew(
1010                 fx.animate(tx, procedure)?,
1011                 fy.animate(ty, procedure)?,
1012             )),
1013             (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t)) => {
1014                 Ok(TransformOperation::SkewX(f.animate(t, procedure)?))
1015             },
1016             (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => {
1017                 Ok(TransformOperation::SkewY(f.animate(t, procedure)?))
1018             },
1019             (
1020                 &TransformOperation::Translate3D(ref fx, ref fy, ref fz),
1021                 &TransformOperation::Translate3D(ref tx, ref ty, ref tz),
1022             ) => Ok(TransformOperation::Translate3D(
1023                 fx.animate(tx, procedure)?,
1024                 fy.animate(ty, procedure)?,
1025                 fz.animate(tz, procedure)?,
1026             )),
1027             (
1028                 &TransformOperation::Translate(ref fx, ref fy),
1029                 &TransformOperation::Translate(ref tx, ref ty),
1030             ) => Ok(TransformOperation::Translate(
1031                 fx.animate(tx, procedure)?,
1032                 fy.animate(ty, procedure)?,
1033             )),
1034             (&TransformOperation::TranslateX(ref f), &TransformOperation::TranslateX(ref t)) => {
1035                 Ok(TransformOperation::TranslateX(f.animate(t, procedure)?))
1036             },
1037             (&TransformOperation::TranslateY(ref f), &TransformOperation::TranslateY(ref t)) => {
1038                 Ok(TransformOperation::TranslateY(f.animate(t, procedure)?))
1039             },
1040             (&TransformOperation::TranslateZ(ref f), &TransformOperation::TranslateZ(ref t)) => {
1041                 Ok(TransformOperation::TranslateZ(f.animate(t, procedure)?))
1042             },
1043             (
1044                 &TransformOperation::Scale3D(ref fx, ref fy, ref fz),
1045                 &TransformOperation::Scale3D(ref tx, ref ty, ref tz),
1046             ) => Ok(TransformOperation::Scale3D(
1047                 animate_multiplicative_factor(*fx, *tx, procedure)?,
1048                 animate_multiplicative_factor(*fy, *ty, procedure)?,
1049                 animate_multiplicative_factor(*fz, *tz, procedure)?,
1050             )),
1051             (&TransformOperation::ScaleX(ref f), &TransformOperation::ScaleX(ref t)) => Ok(
1052                 TransformOperation::ScaleX(animate_multiplicative_factor(*f, *t, procedure)?),
1053             ),
1054             (&TransformOperation::ScaleY(ref f), &TransformOperation::ScaleY(ref t)) => Ok(
1055                 TransformOperation::ScaleY(animate_multiplicative_factor(*f, *t, procedure)?),
1056             ),
1057             (&TransformOperation::ScaleZ(ref f), &TransformOperation::ScaleZ(ref t)) => Ok(
1058                 TransformOperation::ScaleZ(animate_multiplicative_factor(*f, *t, procedure)?),
1059             ),
1060             (
1061                 &TransformOperation::Scale(ref fx, ref fy),
1062                 &TransformOperation::Scale(ref tx, ref ty),
1063             ) => Ok(TransformOperation::Scale(
1064                 animate_multiplicative_factor(*fx, *tx, procedure)?,
1065                 animate_multiplicative_factor(*fy, *ty, procedure)?,
1066             )),
1067             (
1068                 &TransformOperation::Rotate3D(fx, fy, fz, fa),
1069                 &TransformOperation::Rotate3D(tx, ty, tz, ta),
1070             ) => {
1071                 let animated = Rotate::Rotate3D(fx, fy, fz, fa)
1072                     .animate(&Rotate::Rotate3D(tx, ty, tz, ta), procedure)?;
1073                 let (fx, fy, fz, fa) = ComputedRotate::resolve(&animated);
1074                 Ok(TransformOperation::Rotate3D(fx, fy, fz, fa))
1075             },
1076             (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta)) => {
1077                 Ok(TransformOperation::RotateX(fa.animate(&ta, procedure)?))
1078             },
1079             (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta)) => {
1080                 Ok(TransformOperation::RotateY(fa.animate(&ta, procedure)?))
1081             },
1082             (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta)) => {
1083                 Ok(TransformOperation::RotateZ(fa.animate(&ta, procedure)?))
1084             },
1085             (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => {
1086                 Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?))
1087             },
1088             (&TransformOperation::Rotate(fa), &TransformOperation::RotateZ(ta)) => {
1089                 Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?))
1090             },
1091             (&TransformOperation::RotateZ(fa), &TransformOperation::Rotate(ta)) => {
1092                 Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?))
1093             },
1094             (
1095                 &TransformOperation::Perspective(ref fd),
1096                 &TransformOperation::Perspective(ref td),
1097             ) => {
1098                 use crate::values::computed::CSSPixelLength;
1099                 use crate::values::generics::transform::create_perspective_matrix;
1100 
1101                 // From https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions:
1102                 //
1103                 //    The transform functions matrix(), matrix3d() and
1104                 //    perspective() get converted into 4x4 matrices first and
1105                 //    interpolated as defined in section Interpolation of
1106                 //    Matrices afterwards.
1107                 //
1108                 let from = create_perspective_matrix(fd.px());
1109                 let to = create_perspective_matrix(td.px());
1110 
1111                 let interpolated = Matrix3D::from(from).animate(&Matrix3D::from(to), procedure)?;
1112 
1113                 let decomposed = decompose_3d_matrix(interpolated)?;
1114                 let perspective_z = decomposed.perspective.2;
1115                 let used_value = if perspective_z == 0. {
1116                     0.
1117                 } else {
1118                     -1. / perspective_z
1119                 };
1120                 Ok(TransformOperation::Perspective(CSSPixelLength::new(
1121                     used_value,
1122                 )))
1123             },
1124             _ if self.is_translate() && other.is_translate() => self
1125                 .to_translate_3d()
1126                 .animate(&other.to_translate_3d(), procedure),
1127             _ if self.is_scale() && other.is_scale() => {
1128                 self.to_scale_3d().animate(&other.to_scale_3d(), procedure)
1129             },
1130             _ if self.is_rotate() && other.is_rotate() => self
1131                 .to_rotate_3d()
1132                 .animate(&other.to_rotate_3d(), procedure),
1133             _ => Err(()),
1134         }
1135     }
1136 }
1137 
1138 // This might not be the most useful definition of distance. It might be better, for example,
1139 // to trace the distance travelled by a point as its transform is interpolated between the two
1140 // lists. That, however, proves to be quite complicated so we take a simple approach for now.
1141 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1318591#c0.
1142 impl ComputeSquaredDistance for ComputedTransformOperation {
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>1143     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1144         match (self, other) {
1145             (&TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other)) => {
1146                 this.compute_squared_distance(other)
1147             },
1148             (&TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other)) => {
1149                 let this: Matrix3D = (*this).into();
1150                 let other: Matrix3D = (*other).into();
1151                 this.compute_squared_distance(&other)
1152             },
1153             (
1154                 &TransformOperation::Skew(ref fx, ref fy),
1155                 &TransformOperation::Skew(ref tx, ref ty),
1156             ) => Ok(fx.compute_squared_distance(&tx)? + fy.compute_squared_distance(&ty)?),
1157             (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t)) |
1158             (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => {
1159                 f.compute_squared_distance(&t)
1160             },
1161             (
1162                 &TransformOperation::Translate3D(ref fx, ref fy, ref fz),
1163                 &TransformOperation::Translate3D(ref tx, ref ty, ref tz),
1164             ) => {
1165                 // For translate, We don't want to require doing layout in order
1166                 // to calculate the result, so drop the percentage part.
1167                 //
1168                 // However, dropping percentage makes us impossible to compute
1169                 // the distance for the percentage-percentage case, but Gecko
1170                 // uses the same formula, so it's fine for now.
1171                 let basis = Length::new(0.);
1172                 let fx = fx.resolve(basis).px();
1173                 let fy = fy.resolve(basis).px();
1174                 let tx = tx.resolve(basis).px();
1175                 let ty = ty.resolve(basis).px();
1176 
1177                 Ok(fx.compute_squared_distance(&tx)? +
1178                     fy.compute_squared_distance(&ty)? +
1179                     fz.compute_squared_distance(&tz)?)
1180             },
1181             (
1182                 &TransformOperation::Scale3D(ref fx, ref fy, ref fz),
1183                 &TransformOperation::Scale3D(ref tx, ref ty, ref tz),
1184             ) => Ok(fx.compute_squared_distance(&tx)? +
1185                 fy.compute_squared_distance(&ty)? +
1186                 fz.compute_squared_distance(&tz)?),
1187             (
1188                 &TransformOperation::Rotate3D(fx, fy, fz, fa),
1189                 &TransformOperation::Rotate3D(tx, ty, tz, ta),
1190             ) => Rotate::Rotate3D(fx, fy, fz, fa)
1191                 .compute_squared_distance(&Rotate::Rotate3D(tx, ty, tz, ta)),
1192             (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta)) |
1193             (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta)) |
1194             (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta)) |
1195             (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => {
1196                 fa.compute_squared_distance(&ta)
1197             },
1198             (
1199                 &TransformOperation::Perspective(ref fd),
1200                 &TransformOperation::Perspective(ref td),
1201             ) => fd.compute_squared_distance(td),
1202             (&TransformOperation::Perspective(ref p), &TransformOperation::Matrix3D(ref m)) |
1203             (&TransformOperation::Matrix3D(ref m), &TransformOperation::Perspective(ref p)) => {
1204                 // FIXME(emilio): Is this right? Why interpolating this with
1205                 // Perspective but not with anything else?
1206                 let mut p_matrix = Matrix3D::identity();
1207                 if p.px() > 0. {
1208                     p_matrix.m34 = -1. / p.px();
1209                 }
1210                 p_matrix.compute_squared_distance(&m)
1211             },
1212             // Gecko cross-interpolates amongst all translate and all scale
1213             // functions (See ToPrimitive in layout/style/StyleAnimationValue.cpp)
1214             // without falling back to InterpolateMatrix
1215             _ if self.is_translate() && other.is_translate() => self
1216                 .to_translate_3d()
1217                 .compute_squared_distance(&other.to_translate_3d()),
1218             _ if self.is_scale() && other.is_scale() => self
1219                 .to_scale_3d()
1220                 .compute_squared_distance(&other.to_scale_3d()),
1221             _ if self.is_rotate() && other.is_rotate() => self
1222                 .to_rotate_3d()
1223                 .compute_squared_distance(&other.to_rotate_3d()),
1224             _ => Err(()),
1225         }
1226     }
1227 }
1228 
1229 // ------------------------------------
1230 // Individual transforms.
1231 // ------------------------------------
1232 /// <https://drafts.csswg.org/css-transforms-2/#propdef-rotate>
1233 impl ComputedRotate {
resolve(&self) -> (Number, Number, Number, Angle)1234     fn resolve(&self) -> (Number, Number, Number, Angle) {
1235         // According to the spec:
1236         // https://drafts.csswg.org/css-transforms-2/#individual-transforms
1237         //
1238         // If the axis is unspecified, it defaults to "0 0 1"
1239         match *self {
1240             Rotate::None => (0., 0., 1., Angle::zero()),
1241             Rotate::Rotate3D(rx, ry, rz, angle) => (rx, ry, rz, angle),
1242             Rotate::Rotate(angle) => (0., 0., 1., angle),
1243         }
1244     }
1245 }
1246 
1247 impl Animate for ComputedRotate {
1248     #[inline]
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>1249     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1250         match (self, other) {
1251             (&Rotate::None, &Rotate::None) => Ok(Rotate::None),
1252             (&Rotate::Rotate3D(fx, fy, fz, fa), &Rotate::None) => {
1253                 // We always normalize direction vector for rotate3d() first, so we should also
1254                 // apply the same rule for rotate property. In other words, we promote none into
1255                 // a 3d rotate, and normalize both direction vector first, and then do
1256                 // interpolation.
1257                 let (fx, fy, fz, fa) = transform::get_normalized_vector_and_angle(fx, fy, fz, fa);
1258                 Ok(Rotate::Rotate3D(
1259                     fx,
1260                     fy,
1261                     fz,
1262                     fa.animate(&Angle::zero(), procedure)?,
1263                 ))
1264             },
1265             (&Rotate::None, &Rotate::Rotate3D(tx, ty, tz, ta)) => {
1266                 // Normalize direction vector first.
1267                 let (tx, ty, tz, ta) = transform::get_normalized_vector_and_angle(tx, ty, tz, ta);
1268                 Ok(Rotate::Rotate3D(
1269                     tx,
1270                     ty,
1271                     tz,
1272                     Angle::zero().animate(&ta, procedure)?,
1273                 ))
1274             },
1275             (&Rotate::Rotate3D(_, ..), _) | (_, &Rotate::Rotate3D(_, ..)) => {
1276                 let (from, to) = (self.resolve(), other.resolve());
1277                 let (mut fx, mut fy, mut fz, fa) =
1278                     transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3);
1279                 let (mut tx, mut ty, mut tz, ta) =
1280                     transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3);
1281 
1282                 if fa == Angle::from_degrees(0.) {
1283                     fx = tx;
1284                     fy = ty;
1285                     fz = tz;
1286                 } else if ta == Angle::from_degrees(0.) {
1287                     tx = fx;
1288                     ty = fy;
1289                     tz = fz;
1290                 }
1291 
1292                 if (fx, fy, fz) == (tx, ty, tz) {
1293                     return Ok(Rotate::Rotate3D(fx, fy, fz, fa.animate(&ta, procedure)?));
1294                 }
1295 
1296                 let fv = DirectionVector::new(fx, fy, fz);
1297                 let tv = DirectionVector::new(tx, ty, tz);
1298                 let fq = Quaternion::from_direction_and_angle(&fv, fa.radians64());
1299                 let tq = Quaternion::from_direction_and_angle(&tv, ta.radians64());
1300 
1301                 let rq = Quaternion::animate(&fq, &tq, procedure)?;
1302                 let (x, y, z, angle) = transform::get_normalized_vector_and_angle(
1303                     rq.0 as f32,
1304                     rq.1 as f32,
1305                     rq.2 as f32,
1306                     rq.3.acos() as f32 * 2.0,
1307                 );
1308 
1309                 Ok(Rotate::Rotate3D(x, y, z, Angle::from_radians(angle)))
1310             },
1311             (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => {
1312                 // If this is a 2D rotation, we just animate the <angle>
1313                 let (from, to) = (self.resolve().3, other.resolve().3);
1314                 Ok(Rotate::Rotate(from.animate(&to, procedure)?))
1315             },
1316         }
1317     }
1318 }
1319 
1320 impl ComputeSquaredDistance for ComputedRotate {
1321     #[inline]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>1322     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1323         match (self, other) {
1324             (&Rotate::None, &Rotate::None) => Ok(SquaredDistance::from_sqrt(0.)),
1325             (&Rotate::Rotate3D(_, _, _, a), &Rotate::None) |
1326             (&Rotate::None, &Rotate::Rotate3D(_, _, _, a)) => {
1327                 a.compute_squared_distance(&Angle::zero())
1328             },
1329             (&Rotate::Rotate3D(_, ..), _) | (_, &Rotate::Rotate3D(_, ..)) => {
1330                 let (from, to) = (self.resolve(), other.resolve());
1331                 let (mut fx, mut fy, mut fz, angle1) =
1332                     transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3);
1333                 let (mut tx, mut ty, mut tz, angle2) =
1334                     transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3);
1335 
1336                 if angle1 == Angle::zero() {
1337                     fx = tx;
1338                     fy = ty;
1339                     fz = tz;
1340                 } else if angle2 == Angle::zero() {
1341                     tx = fx;
1342                     ty = fy;
1343                     tz = fz;
1344                 }
1345 
1346                 if (fx, fy, fz) == (tx, ty, tz) {
1347                     angle1.compute_squared_distance(&angle2)
1348                 } else {
1349                     let v1 = DirectionVector::new(fx, fy, fz);
1350                     let v2 = DirectionVector::new(tx, ty, tz);
1351                     let q1 = Quaternion::from_direction_and_angle(&v1, angle1.radians64());
1352                     let q2 = Quaternion::from_direction_and_angle(&v2, angle2.radians64());
1353                     q1.compute_squared_distance(&q2)
1354                 }
1355             },
1356             (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => self
1357                 .resolve()
1358                 .3
1359                 .compute_squared_distance(&other.resolve().3),
1360         }
1361     }
1362 }
1363 
1364 /// <https://drafts.csswg.org/css-transforms-2/#propdef-translate>
1365 impl ComputedTranslate {
resolve(&self) -> (LengthPercentage, LengthPercentage, Length)1366     fn resolve(&self) -> (LengthPercentage, LengthPercentage, Length) {
1367         // According to the spec:
1368         // https://drafts.csswg.org/css-transforms-2/#individual-transforms
1369         //
1370         // Unspecified translations default to 0px
1371         match *self {
1372             Translate::None => (
1373                 LengthPercentage::zero(),
1374                 LengthPercentage::zero(),
1375                 Length::zero(),
1376             ),
1377             Translate::Translate(ref tx, ref ty, ref tz) => (tx.clone(), ty.clone(), tz.clone()),
1378         }
1379     }
1380 }
1381 
1382 impl Animate for ComputedTranslate {
1383     #[inline]
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>1384     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1385         match (self, other) {
1386             (&Translate::None, &Translate::None) => Ok(Translate::None),
1387             (&Translate::Translate(_, ..), _) | (_, &Translate::Translate(_, ..)) => {
1388                 let (from, to) = (self.resolve(), other.resolve());
1389                 Ok(Translate::Translate(
1390                     from.0.animate(&to.0, procedure)?,
1391                     from.1.animate(&to.1, procedure)?,
1392                     from.2.animate(&to.2, procedure)?,
1393                 ))
1394             },
1395         }
1396     }
1397 }
1398 
1399 impl ComputeSquaredDistance for ComputedTranslate {
1400     #[inline]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>1401     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1402         let (from, to) = (self.resolve(), other.resolve());
1403         Ok(from.0.compute_squared_distance(&to.0)? +
1404             from.1.compute_squared_distance(&to.1)? +
1405             from.2.compute_squared_distance(&to.2)?)
1406     }
1407 }
1408 
1409 /// <https://drafts.csswg.org/css-transforms-2/#propdef-scale>
1410 impl ComputedScale {
resolve(&self) -> (Number, Number, Number)1411     fn resolve(&self) -> (Number, Number, Number) {
1412         // According to the spec:
1413         // https://drafts.csswg.org/css-transforms-2/#individual-transforms
1414         //
1415         // Unspecified scales default to 1
1416         match *self {
1417             Scale::None => (1.0, 1.0, 1.0),
1418             Scale::Scale(sx, sy, sz) => (sx, sy, sz),
1419         }
1420     }
1421 }
1422 
1423 impl Animate for ComputedScale {
1424     #[inline]
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>1425     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1426         match (self, other) {
1427             (&Scale::None, &Scale::None) => Ok(Scale::None),
1428             (&Scale::Scale(_, ..), _) | (_, &Scale::Scale(_, ..)) => {
1429                 let (from, to) = (self.resolve(), other.resolve());
1430                 // For transform lists, we add by appending to the list of
1431                 // transform functions. However, ComputedScale cannot be
1432                 // simply concatenated, so we have to calculate the additive
1433                 // result here.
1434                 if procedure == Procedure::Add {
1435                     // scale(x1,y1,z1)*scale(x2,y2,z2) = scale(x1*x2, y1*y2, z1*z2)
1436                     return Ok(Scale::Scale(from.0 * to.0, from.1 * to.1, from.2 * to.2));
1437                 }
1438                 Ok(Scale::Scale(
1439                     animate_multiplicative_factor(from.0, to.0, procedure)?,
1440                     animate_multiplicative_factor(from.1, to.1, procedure)?,
1441                     animate_multiplicative_factor(from.2, to.2, procedure)?,
1442                 ))
1443             },
1444         }
1445     }
1446 }
1447 
1448 impl ComputeSquaredDistance for ComputedScale {
1449     #[inline]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>1450     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
1451         let (from, to) = (self.resolve(), other.resolve());
1452         Ok(from.0.compute_squared_distance(&to.0)? +
1453             from.1.compute_squared_distance(&to.1)? +
1454             from.2.compute_squared_distance(&to.2)?)
1455     }
1456 }
1457