Bug 1272549 - Part 7: Compute distance for matrix and matrix3d. draft
authorBoris Chiou <boris.chiou@gmail.com>
Tue, 04 Oct 2016 18:32:20 +0800
changeset 428019 ad44760225a6229a7bc1eef231683e3acd90da1e
parent 428018 cf5095241aaed8baa8294bac3055547a5491a192
child 428020 f1c1f056c462f3fdd6109f765e92ea4e1303886b
push id33211
push userbmo:boris.chiou@gmail.com
push dateFri, 21 Oct 2016 13:32:53 +0000
bugs1272549
milestone52.0a1
Bug 1272549 - Part 7: Compute distance for matrix and matrix3d. Decompose the 2d/3d matrix and calculate the Euclidean distance of translate, scale, skew, quaternion, and perspective vectors. MozReview-Commit-ID: 5nX9EeqvZi9
gfx/2d/Matrix.h
layout/style/StyleAnimationValue.cpp
layout/style/nsStyleTransformMatrix.cpp
layout/style/nsStyleTransformMatrix.h
--- a/gfx/2d/Matrix.h
+++ b/gfx/2d/Matrix.h
@@ -8,16 +8,17 @@
 
 #include "Types.h"
 #include "Triangle.h"
 #include "Rect.h"
 #include "Point.h"
 #include "Quaternion.h"
 #include <iosfwd>
 #include <math.h>
+#include "mozilla/Array.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/FloatingPoint.h"
 
 namespace mozilla {
 namespace gfx {
 
 static bool FuzzyEqual(Float aV1, Float aV2) {
@@ -458,16 +459,21 @@ public:
                  Float a31, Float a32, Float a33, Float a34,
                  Float a41, Float a42, Float a43, Float a44)
     : _11(a11), _12(a12), _13(a13), _14(a14)
     , _21(a21), _22(a22), _23(a23), _24(a24)
     , _31(a31), _32(a32), _33(a33), _34(a34)
     , _41(a41), _42(a42), _43(a43), _44(a44)
   {}
 
+  explicit Matrix4x4Typed(const Float aArray[16])
+  {
+    memcpy(components, aArray, sizeof(components));
+  }
+
   Matrix4x4Typed(const Matrix4x4Typed& aOther)
   {
     memcpy(this, &aOther, sizeof(*this));
   }
 
   union {
     struct {
       Float _11, _12, _13, _14;
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -721,16 +721,122 @@ StyleAnimationValue::ComputeColorDistanc
   return sqrt(diffA * diffA + diffR * diffR + diffG * diffG + diffB * diffB);
 }
 
 static nsCSSValueList*
 AddTransformLists(double aCoeff1, const nsCSSValueList* aList1,
                   double aCoeff2, const nsCSSValueList* aList2);
 
 static double
+ComputeTransform2DMatrixDistance(const Matrix& aMatrix1,
+                                 const Matrix& aMatrix2)
+{
+  Point3D scale1(1, 1, 1);
+  Point3D translate1;
+  gfxQuaternion rotate1;
+  nsStyleTransformMatrix::ShearArray shear1;
+  for (auto&& s : shear1) {
+    s = 0.0f;
+  }
+  Decompose2DMatrix(aMatrix1, scale1, shear1, rotate1, translate1);
+
+  Point3D scale2(1, 1, 1);
+  Point3D translate2;
+  gfxQuaternion rotate2;
+  nsStyleTransformMatrix::ShearArray shear2;
+  for (auto&& s : shear2) {
+    s = 0.0f;
+  }
+  Decompose2DMatrix(aMatrix2, scale2, shear2, rotate2, translate2);
+
+  // Note:
+  // 1. Shear factor is the tangent value of shear angle, so we need to
+  //    call atan() to get the angle. For 2D transform, we only have XYSHEAR.
+  // 2. The quaternion vector of the decomposed 2d matrix is got by
+  //    "gfxQuaternion(0, 0, sin(rotate/2), cos(rotate/2))"
+  //                         ^^^^^^^^^^^^^  ^^^^^^^^^^^^^
+  //                               z              w
+  //    Therefore, we can get the rotate angle by 2 * atan2f(z, w).
+  //    However, we can also get the rotate angle by the inner product of
+  //    two quaternion vectors, just as what we do for eCSSKeyword_rotate3d.
+  //    e.g.
+  //      rotate3d(0, 0, 1, 60deg)  =>  rotate3d(0, 0, 1, 120deg);
+  //      quaternion 1: (0, 0, sin(30deg), cos(30deg)) = (0, 0, 1/2, sqrt(3)/2)
+  //      quaternion 2: (0, 0, sin(60deg), cos(60deg)) = (0, 0, sqrt(3)/2, 1/2)
+  //      inner product:  sqrt(3)/4 + sqrt(3)/4 = sqrt(3)/2
+  //      Finally, the rotate angle: 2 * acos(sqrt(3)/2) = 60deg
+  //
+  //    I think doing atan() may be faster than doing inner product together
+  //    with acos(), so we adopt atan2f() here.
+  const Point3D diffTranslate = translate2 - translate1;
+  const Point3D diffScale = scale2 - scale1;
+  const double diffShear = atan(shear2[ShearType::XYSHEAR]) -
+                           atan(shear1[ShearType::XYSHEAR]);
+  const double diffRotate = 2.0 * (atan2f(rotate2.z, rotate2.w) -
+                                   atan2f(rotate1.z, rotate1.w));
+  // Returns the sum of squares because we will take a square root in
+  // ComputeTransformListDistance.
+  return diffTranslate.DotProduct(diffTranslate) +
+         diffScale.DotProduct(diffScale) +
+         diffRotate * diffRotate +
+         diffShear * diffShear;
+}
+
+static double
+ComputeTransform3DMatrixDistance(const Matrix4x4& aMatrix1,
+                                 const Matrix4x4& aMatrix2)
+{
+  Point3D scale1(1, 1, 1);
+  Point3D translate1;
+  Point4D perspective1(0, 0, 0, 1);
+  gfxQuaternion rotate1;
+  nsStyleTransformMatrix::ShearArray shear1;
+  for (auto&& s : shear1) {
+    s = 0.0f;
+  }
+  Decompose3DMatrix(aMatrix1, scale1, shear1, rotate1, translate1,
+                    perspective1);
+
+  Point3D scale2(1, 1, 1);
+  Point3D translate2;
+  Point4D perspective2(0, 0, 0, 1);
+  gfxQuaternion rotate2;
+  nsStyleTransformMatrix::ShearArray shear2;
+  for (auto&& s : shear2) {
+    s = 0.0f;
+  }
+  Decompose3DMatrix(aMatrix2, scale2, shear2, rotate2, translate2,
+                    perspective2);
+
+  // Note:
+  // 1. Shear factor is the tangent value of shear angle, so we need to
+  //    call atan() to get the angle.
+  // 2. We use the same way to get the rotate angle of two quaternion vectors as
+  //    what we do for rotate3d.
+  const Point3D diffTranslate = translate2 - translate1;
+  const Point3D diffScale = scale2 - scale1;
+  const Point3D diffShear(atan(shear2[ShearType::XYSHEAR]) -
+                            atan(shear1[ShearType::XYSHEAR]),
+                          atan(shear2[ShearType::XZSHEAR]) -
+                            atan(shear1[ShearType::XZSHEAR]),
+                          atan(shear2[ShearType::YZSHEAR]) -
+                            atan(shear1[ShearType::YZSHEAR]));
+  const Point4D diffPerspective = perspective2 - perspective1;
+  const double dot = clamped(rotate1.DotProduct(rotate2), -1.0, 1.0);
+  const double diffRotate = 2.0 * acos(dot);
+  // Returns the sum of squares because we will take a square root in
+  // ComputeTransformListDistance.
+  return diffTranslate.DotProduct(diffTranslate) +
+         diffScale.DotProduct(diffScale) +
+         diffPerspective.DotProduct(diffPerspective) +
+         diffShear.DotProduct(diffShear) +
+         diffRotate * diffRotate;
+}
+
+static double
 ComputeTransformDistance(nsCSSValue::Array* aArray1,
                          nsCSSValue::Array* aArray2)
 {
   MOZ_ASSERT(aArray1, "aArray1 should be non-null.");
   MOZ_ASSERT(aArray2, "aArray2 should be non-null.");
 
   // Normalize translate and scale functions to equivalent "translate3d" and
   // "scale3d" functions.
@@ -831,22 +937,34 @@ ComputeTransformDistance(nsCSSValue::Arr
         distance = distance * distance;
       }
       break;
     }
     case eCSSKeyword_perspective:
       // TODO: This will be fixed in the later patch.
       distance = 0.0;
       break;
-    case eCSSKeyword_matrix:
-    case eCSSKeyword_matrix3d:
-      // TODO: decompose matrix and calculate distance. This will be fixed in
-      // the later patch.
-      distance = 0.0;
+    case eCSSKeyword_matrix: {
+      MOZ_ASSERT(a1->Count() == 7, "unexpected count");
+      MOZ_ASSERT(a2->Count() == 7, "unexpected count");
+
+      distance = ComputeTransform2DMatrixDistance(
+        nsStyleTransformMatrix::CSSValueArrayTo2DMatrix(a1),
+        nsStyleTransformMatrix::CSSValueArrayTo2DMatrix(a2));
       break;
+    }
+    case eCSSKeyword_matrix3d: {
+      MOZ_ASSERT(a1->Count() == 17, "unexpected count");
+      MOZ_ASSERT(a2->Count() == 17, "unexpected count");
+
+      distance = ComputeTransform3DMatrixDistance(
+        nsStyleTransformMatrix::CSSValueArrayTo3DMatrix(a1),
+        nsStyleTransformMatrix::CSSValueArrayTo3DMatrix(a2));
+      break;
+    }
     case eCSSKeyword_interpolatematrix:
     default:
       MOZ_ASSERT_UNREACHABLE("Unsupported transform function");
       break;
   }
   return distance;
 }
 
--- a/layout/style/nsStyleTransformMatrix.cpp
+++ b/layout/style/nsStyleTransformMatrix.cpp
@@ -1028,9 +1028,38 @@ Decompose3DMatrix(const Matrix4x4& aMatr
   }
 
   /* Now, get the rotations out */
   aRotate = gfxQuaternion(local);
 
   return true;
 }
 
+Matrix
+CSSValueArrayTo2DMatrix(nsCSSValue::Array* aArray)
+{
+  MOZ_ASSERT(aArray &&
+             TransformFunctionOf(aArray) == eCSSKeyword_matrix &&
+             aArray->Count() == 7);
+  Matrix m(aArray->Item(1).GetFloatValue(),
+           aArray->Item(2).GetFloatValue(),
+           aArray->Item(3).GetFloatValue(),
+           aArray->Item(4).GetFloatValue(),
+           aArray->Item(5).GetFloatValue(),
+           aArray->Item(6).GetFloatValue());
+  return m;
+}
+
+Matrix4x4
+CSSValueArrayTo3DMatrix(nsCSSValue::Array* aArray)
+{
+  MOZ_ASSERT(aArray &&
+             TransformFunctionOf(aArray) == eCSSKeyword_matrix3d &&
+             aArray->Count() == 17);
+  gfx::Float array[16];
+  for (size_t i = 0; i < 16; ++i) {
+    array[i] = aArray->Item(i+1).GetFloatValue();
+  }
+  Matrix4x4 m(array);
+  return m;
+}
+
 } // namespace nsStyleTransformMatrix
--- a/layout/style/nsStyleTransformMatrix.h
+++ b/layout/style/nsStyleTransformMatrix.h
@@ -196,11 +196,14 @@ namespace nsStyleTransformMatrix {
    * Implements the 3d transform matrix decomposition algorithm.
    */
   bool Decompose3DMatrix(const mozilla::gfx::Matrix4x4& aMatrix,
                          mozilla::gfx::Point3D& aScale,
                          ShearArray& aShear,
                          gfxQuaternion& aRotate,
                          mozilla::gfx::Point3D& aTranslate,
                          mozilla::gfx::Point4D& aPerspective);
+
+  mozilla::gfx::Matrix CSSValueArrayTo2DMatrix(nsCSSValue::Array* aArray);
+  mozilla::gfx::Matrix4x4 CSSValueArrayTo3DMatrix(nsCSSValue::Array* aArray);
 } // namespace nsStyleTransformMatrix
 
 #endif