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