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