Bug 1353202 - Support accumulation of transform lists draft
authorBrian Birtles <birtles@gmail.com>
Tue, 23 May 2017 08:19:50 +0900
changeset 582812 52ad060326bafda3eadc0539f65a6b098608538d
parent 582811 07690c0ab94fe35ea7233c29cb3a0989849f5967
child 582813 dfb7cb441923920a4b4bf606020c7fde538f9fb3
child 582819 0b828217edea9e1118e04a178d40aa5ad0cae174
push id60186
push userbbirtles@mozilla.com
push dateTue, 23 May 2017 05:52:17 +0000
bugs1353202
milestone55.0a1
Bug 1353202 - Support accumulation of transform lists MozReview-Commit-ID: 1NpjM2HcYjF
servo/components/style/properties/helpers/animated_properties.mako.rs
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -1663,16 +1663,31 @@ fn build_identity_transform_list(list: &
                 result.push(TransformOperation::Matrix(identity));
             }
         }
     }
 
     result
 }
 
+/// A wrapper for calling add_weighted that interpolates the distance of the two values from
+/// an initial_value and uses that to produce an interpolated value.
+/// This is used for values such as 'scale' where the initial value is 1 and where if we interpolate
+/// the absolute values, we will produce odd results for accumulation.
+fn add_weighted_with_initial_val<T: Animatable>(a: &T,
+                                                b: &T,
+                                                a_portion: f64,
+                                                b_portion: f64,
+                                                initial_val: &T) -> Result<T, ()> {
+    let a = try!(a.add_weighted(&initial_val, 1.0, -1.0));
+    let b = try!(b.add_weighted(&initial_val, 1.0, -1.0));
+    let result = try!(a.add_weighted(&b, a_portion, b_portion));
+    result.add_weighted(&initial_val, 1.0, 1.0)
+}
+
 /// Add two transform lists.
 /// http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms
 fn add_weighted_transform_lists(from_list: &[TransformOperation],
                                 to_list: &[TransformOperation],
                                 self_portion: f64,
                                 other_portion: f64) -> TransformList {
     let mut result = vec![];
 
@@ -1700,19 +1715,22 @@ fn add_weighted_transform_lists(from_lis
                  &TransformOperation::Translate(tx, ty, tz)) => {
                     let ix = fx.add_weighted(&tx, self_portion, other_portion).unwrap();
                     let iy = fy.add_weighted(&ty, self_portion, other_portion).unwrap();
                     let iz = fz.add_weighted(&tz, self_portion, other_portion).unwrap();
                     result.push(TransformOperation::Translate(ix, iy, iz));
                 }
                 (&TransformOperation::Scale(fx, fy, fz),
                  &TransformOperation::Scale(tx, ty, tz)) => {
-                    let ix = fx.add_weighted(&tx, self_portion, other_portion).unwrap();
-                    let iy = fy.add_weighted(&ty, self_portion, other_portion).unwrap();
-                    let iz = fz.add_weighted(&tz, self_portion, other_portion).unwrap();
+                    let ix = add_weighted_with_initial_val(&fx, &tx, self_portion,
+                                                           other_portion, &1.0).unwrap();
+                    let iy = add_weighted_with_initial_val(&fy, &ty, self_portion,
+                                                           other_portion, &1.0).unwrap();
+                    let iz = add_weighted_with_initial_val(&fz, &tz, self_portion,
+                                                           other_portion, &1.0).unwrap();
                     result.push(TransformOperation::Scale(ix, iy, iz));
                 }
                 (&TransformOperation::Rotate(fx, fy, fz, fa),
                  &TransformOperation::Rotate(tx, ty, tz, ta)) => {
                     let norm_f = ((fx * fx) + (fy * fy) + (fz * fz)).sqrt();
                     let norm_t = ((tx * tx) + (ty * ty) + (tz * tz)).sqrt();
                     let (fx, fy, fz) = (fx / norm_f, fy / norm_f, fz / norm_f);
                     let (tx, ty, tz) = (tx / norm_t, ty / norm_t, tz / norm_t);
@@ -1812,38 +1830,40 @@ pub struct MatrixDecomposed2D {
     pub angle: f32,
     /// The inner matrix.
     pub matrix: InnerMatrix2D,
 }
 
 impl Animatable for InnerMatrix2D {
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok(InnerMatrix2D {
-            m11: try!(self.m11.add_weighted(&other.m11, self_portion, other_portion)),
+            m11: try!(add_weighted_with_initial_val(&self.m11, &other.m11,
+                                                    self_portion, other_portion, &1.0)),
             m12: try!(self.m12.add_weighted(&other.m12, self_portion, other_portion)),
             m21: try!(self.m21.add_weighted(&other.m21, self_portion, other_portion)),
-            m22: try!(self.m22.add_weighted(&other.m22, self_portion, other_portion)),
+            m22: try!(add_weighted_with_initial_val(&self.m22, &other.m22,
+                                                    self_portion, other_portion, &1.0)),
         })
     }
 }
 
 impl Animatable for Translate2D {
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok(Translate2D(
             try!(self.0.add_weighted(&other.0, self_portion, other_portion)),
             try!(self.1.add_weighted(&other.1, self_portion, other_portion))
         ))
     }
 }
 
 impl Animatable for Scale2D {
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok(Scale2D(
-            try!(self.0.add_weighted(&other.0, self_portion, other_portion)),
-            try!(self.1.add_weighted(&other.1, self_portion, other_portion))
+            try!(add_weighted_with_initial_val(&self.0, &other.0, self_portion, other_portion, &1.0)),
+            try!(add_weighted_with_initial_val(&self.1, &other.1, self_portion, other_portion, &1.0))
         ))
     }
 }
 
 impl Animatable for MatrixDecomposed2D {
     /// https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-2d-matrix-values
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         // If x-axis of one is flipped, and y-axis of the other,
@@ -2233,19 +2253,19 @@ impl Animatable for Translate3D {
             try!(self.2.add_weighted(&other.2, self_portion, other_portion))
         ))
     }
 }
 
 impl Animatable for Scale3D {
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok(Scale3D(
-            try!(self.0.add_weighted(&other.0, self_portion, other_portion)),
-            try!(self.1.add_weighted(&other.1, self_portion, other_portion)),
-            try!(self.2.add_weighted(&other.2, self_portion, other_portion))
+            try!(add_weighted_with_initial_val(&self.0, &other.0, self_portion, other_portion, &1.0)),
+            try!(add_weighted_with_initial_val(&self.1, &other.1, self_portion, other_portion, &1.0)),
+            try!(add_weighted_with_initial_val(&self.2, &other.2, self_portion, other_portion, &1.0))
         ))
     }
 }
 
 impl Animatable for Skew {
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok(Skew(
             try!(self.0.add_weighted(&other.0, self_portion, other_portion)),
@@ -2256,63 +2276,97 @@ impl Animatable for Skew {
 }
 
 impl Animatable for Perspective {
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok(Perspective(
             try!(self.0.add_weighted(&other.0, self_portion, other_portion)),
             try!(self.1.add_weighted(&other.1, self_portion, other_portion)),
             try!(self.2.add_weighted(&other.2, self_portion, other_portion)),
-            try!(self.3.add_weighted(&other.3, self_portion, other_portion))
+            try!(add_weighted_with_initial_val(&self.3, &other.3, self_portion, other_portion, &1.0))
         ))
     }
 }
 
 impl Animatable for MatrixDecomposed3D {
     /// https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-3d-matrix-values
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
         -> Result<Self, ()> {
-        assert!(self_portion + other_portion == 1.0f64,
-                "add_weighted should only be used for interpolating transforms");
+        assert!(self_portion + other_portion == 1.0f64 ||
+                other_portion == 1.0f64,
+                "add_weighted should only be used for interpolating or accumulating transforms");
 
         let mut sum = *self;
 
         // Add translate, scale, skew and perspective components.
         sum.translate = try!(self.translate.add_weighted(&other.translate,
                                                          self_portion, other_portion));
         sum.scale = try!(self.scale.add_weighted(&other.scale, self_portion, other_portion));
         sum.skew = try!(self.skew.add_weighted(&other.skew, self_portion, other_portion));
         sum.perspective = try!(self.perspective.add_weighted(&other.perspective,
                                                              self_portion, other_portion));
 
         // Add quaternions using spherical linear interpolation (Slerp).
-        let mut product = self.quaternion.0 * other.quaternion.0 +
-                          self.quaternion.1 * other.quaternion.1 +
-                          self.quaternion.2 * other.quaternion.2 +
-                          self.quaternion.3 * other.quaternion.3;
+        //
+        // We take a specialized code path for accumulation (where other_portion is 1)
+        if other_portion == 1.0 {
+            if self_portion == 0.0 {
+                return Ok(*other)
+            }
+
+            let clamped_w = self.quaternion.3.min(1.0).max(-1.0);
+
+            // Determine the scale factor.
+            let mut theta = clamped_w.acos();
+            let mut scale = match theta { 0.0 => 0.0, _ => 1.0 / theta.sin() };
+            theta *= self_portion as f32;
+            scale *= theta.sin();
+
+            // Scale the self matrix by self_portion.
+            let mut scaled_self = *self;
+            % for i in range(3):
+                scaled_self.quaternion.${i} *= scale;
+            % endfor
+            scaled_self.quaternion.3 = theta.cos();
 
-        // Clamp product to -1.0 <= product <= 1.0
-        product = product.min(1.0);
-        product = product.max(-1.0);
+            // Multiply scaled-self by other.
+            let a = &scaled_self.quaternion;
+            let b = &other.quaternion;
+            sum.quaternion = Quaternion(
+                a.3 * b.0 + a.0 * b.3 + a.1 * b.2 - a.2 * b.1,
+                a.3 * b.1 - a.0 * b.2 + a.1 * b.3 + a.2 * b.0,
+                a.3 * b.2 + a.0 * b.1 - a.1 * b.0 + a.2 * b.3,
+                a.3 * b.3 - a.0 * b.0 - a.1 * b.1 - a.2 * b.2,
+            );
+        } else {
+            let mut product = self.quaternion.0 * other.quaternion.0 +
+                              self.quaternion.1 * other.quaternion.1 +
+                              self.quaternion.2 * other.quaternion.2 +
+                              self.quaternion.3 * other.quaternion.3;
 
-        if product == 1.0 {
-            return Ok(sum);
+            // Clamp product to -1.0 <= product <= 1.0
+            product = product.min(1.0);
+            product = product.max(-1.0);
+
+            if product == 1.0 {
+                return Ok(sum);
+            }
+
+            let theta = product.acos();
+            let w = (other_portion as f32 * theta).sin() * 1.0 / (1.0 - product * product).sqrt();
+
+            let mut a = *self;
+            let mut b = *other;
+            % for i in range(4):
+                a.quaternion.${i} *= (other_portion as f32 * theta).cos() - product * w;
+                b.quaternion.${i} *= w;
+                sum.quaternion.${i} = a.quaternion.${i} + b.quaternion.${i};
+            % endfor
         }
 
-        let theta = product.acos();
-        let w = (other_portion as f32 * theta).sin() * 1.0 / (1.0 - product * product).sqrt();
-
-        let mut a = *self;
-        let mut b = *other;
-        % for i in range(4):
-            a.quaternion.${i} *= (other_portion as f32 * theta).cos() - product * w;
-            b.quaternion.${i} *= w;
-            sum.quaternion.${i} = a.quaternion.${i} + b.quaternion.${i};
-        % endfor
-
         Ok(sum)
     }
 }
 
 impl From<MatrixDecomposed3D> for ComputedMatrix {
     /// Recompose a 3D matrix.
     /// https://drafts.csswg.org/css-transforms/#recomposing-to-a-3d-matrix
     fn from(decomposed: MatrixDecomposed3D) -> ComputedMatrix {