text: // Copyright 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #define _USE_MATH_DEFINES
  #include <math.h>
  
  #include "testing/gtest/include/gtest/gtest.h"
  #include "third_party/WebKit/Source/Platform/chromium/public/WebTransformationMatrix.h"
  
  #define EXPECT_ROW1_EQ(a, b, c, d, matrix)                                     \
    EXPECT_FLOAT_EQ((a), (matrix).m11());                                        \
    EXPECT_FLOAT_EQ((b), (matrix).m21());                                        \
    EXPECT_FLOAT_EQ((c), (matrix).m31());                                        \
    EXPECT_FLOAT_EQ((d), (matrix).m41());
  
  #define EXPECT_ROW2_EQ(a, b, c, d, matrix)                                     \
    EXPECT_FLOAT_EQ((a), (matrix).m12());                                        \
    EXPECT_FLOAT_EQ((b), (matrix).m22());                                        \
    EXPECT_FLOAT_EQ((c), (matrix).m32());                                        \
    EXPECT_FLOAT_EQ((d), (matrix).m42());
  
  #define EXPECT_ROW3_EQ(a, b, c, d, matrix)                                     \
    EXPECT_FLOAT_EQ((a), (matrix).m13());                                        \
    EXPECT_FLOAT_EQ((b), (matrix).m23());                                        \
    EXPECT_FLOAT_EQ((c), (matrix).m33());                                        \
    EXPECT_FLOAT_EQ((d), (matrix).m43());
  
  #define EXPECT_ROW4_EQ(a, b, c, d, matrix)                                     \
    EXPECT_FLOAT_EQ((a), (matrix).m14());                                        \
    EXPECT_FLOAT_EQ((b), (matrix).m24());                                        \
    EXPECT_FLOAT_EQ((c), (matrix).m34());                                        \
    EXPECT_FLOAT_EQ((d), (matrix).m44());
  
  #define EXPECT_WEB_TRANSFORMATION_MATRIX_EQ(expected, actual)                  \
    EXPECT_FLOAT_EQ((expected).m11(), (actual).m11());                           \
    EXPECT_FLOAT_EQ((expected).m12(), (actual).m12());                           \
    EXPECT_FLOAT_EQ((expected).m13(), (actual).m13());                           \
    EXPECT_FLOAT_EQ((expected).m14(), (actual).m14());                           \
    EXPECT_FLOAT_EQ((expected).m21(), (actual).m21());                           \
    EXPECT_FLOAT_EQ((expected).m22(), (actual).m22());                           \
    EXPECT_FLOAT_EQ((expected).m23(), (actual).m23());                           \
    EXPECT_FLOAT_EQ((expected).m24(), (actual).m24());                           \
    EXPECT_FLOAT_EQ((expected).m31(), (actual).m31());                           \
    EXPECT_FLOAT_EQ((expected).m32(), (actual).m32());                           \
    EXPECT_FLOAT_EQ((expected).m33(), (actual).m33());                           \
    EXPECT_FLOAT_EQ((expected).m34(), (actual).m34());                           \
    EXPECT_FLOAT_EQ((expected).m41(), (actual).m41());                           \
    EXPECT_FLOAT_EQ((expected).m42(), (actual).m42());                           \
    EXPECT_FLOAT_EQ((expected).m43(), (actual).m43());                           \
    EXPECT_FLOAT_EQ((expected).m44(), (actual).m44());
  
  // Checking float values for equality close to zero is not robust using EXPECT_FLOAT_EQ
  // (see gtest documentation). So, to verify rotation matrices, we must use a looser
  // absolute error threshold in some places.
  #define EXPECT_ROW1_NEAR(a, b, c, d, matrix, error_threshold)                  \
    EXPECT_NEAR((a), (matrix).m11(), (error_threshold));                         \
    EXPECT_NEAR((b), (matrix).m21(), (error_threshold));                         \
    EXPECT_NEAR((c), (matrix).m31(), (error_threshold));                         \
    EXPECT_NEAR((d), (matrix).m41(), (error_threshold));
  
  #define EXPECT_ROW2_NEAR(a, b, c, d, matrix, error_threshold)                  \
    EXPECT_NEAR((a), (matrix).m12(), (error_threshold));                         \
    EXPECT_NEAR((b), (matrix).m22(), (error_threshold));                         \
    EXPECT_NEAR((c), (matrix).m32(), (error_threshold));                         \
    EXPECT_NEAR((d), (matrix).m42(), (error_threshold));
  
  #define EXPECT_ROW3_NEAR(a, b, c, d, matrix, error_threshold)                  \
    EXPECT_NEAR((a), (matrix).m13(), (error_threshold));                         \
    EXPECT_NEAR((b), (matrix).m23(), (error_threshold));                         \
    EXPECT_NEAR((c), (matrix).m33(), (error_threshold));                         \
    EXPECT_NEAR((d), (matrix).m43(), (error_threshold));
  
  #define ERROR_THRESHOLD 1e-14
  #define LOOSE_ERROR_THRESHOLD 1e-7
  
  using namespace WebKit;
  
  namespace {
  
  static void initializeTestMatrix(WebTransformationMatrix& transform) {
    transform.setM11(10);
    transform.setM12(11);
    transform.setM13(12);
    transform.setM14(13);
    transform.setM21(14);
    transform.setM22(15);
    transform.setM23(16);
    transform.setM24(17);
    transform.setM31(18);
    transform.setM32(19);
    transform.setM33(20);
    transform.setM34(21);
    transform.setM41(22);
    transform.setM42(23);
    transform.setM43(24);
    transform.setM44(25);
  
    // Sanity check
    EXPECT_ROW1_EQ(10, 14, 18, 22, transform);
    EXPECT_ROW2_EQ(11, 15, 19, 23, transform);
    EXPECT_ROW3_EQ(12, 16, 20, 24, transform);
    EXPECT_ROW4_EQ(13, 17, 21, 25, transform);
  }
  
  static void initializeTestMatrix2(WebTransformationMatrix& transform) {
    transform.setM11(30);
    transform.setM12(31);
    transform.setM13(32);
    transform.setM14(33);
    transform.setM21(34);
    transform.setM22(35);
    transform.setM23(36);
    transform.setM24(37);
    transform.setM31(38);
    transform.setM32(39);
    transform.setM33(40);
    transform.setM34(41);
    transform.setM41(42);
    transform.setM42(43);
    transform.setM43(44);
    transform.setM44(45);
  
    // Sanity check
    EXPECT_ROW1_EQ(30, 34, 38, 42, transform);
    EXPECT_ROW2_EQ(31, 35, 39, 43, transform);
    EXPECT_ROW3_EQ(32, 36, 40, 44, transform);
    EXPECT_ROW4_EQ(33, 37, 41, 45, transform);
  }
  
  TEST(WebTransformationMatrixTest, DefaultConstructorCreatesIdentityMatrix) {
    WebTransformationMatrix A;
    EXPECT_ROW1_EQ(1, 0, 0, 0, A);
    EXPECT_ROW2_EQ(0, 1, 0, 0, A);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
    EXPECT_TRUE(A.isIdentity());
  }
  
  TEST(WebTransformationMatrixTest, ConstructorFor2dElements) {
    WebTransformationMatrix A(1, 2, 3, 4, 5, 6);
    EXPECT_ROW1_EQ(1, 3, 0, 5, A);
    EXPECT_ROW2_EQ(2, 4, 0, 6, A);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, ConstructorForAllElements) {
    WebTransformationMatrix A(
        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
    EXPECT_ROW1_EQ(1, 5, 9, 13, A);
    EXPECT_ROW2_EQ(2, 6, 10, 14, A);
    EXPECT_ROW3_EQ(3, 7, 11, 15, A);
    EXPECT_ROW4_EQ(4, 8, 12, 16, A);
  }
  
  TEST(WebTransformationMatrixTest, CopyConstructor) {
    WebTransformationMatrix A;
    initializeTestMatrix(A);
  
    // Copy constructor should produce exact same elements as matrix A.
    WebTransformationMatrix B(A);
    EXPECT_ROW1_EQ(10, 14, 18, 22, B);
    EXPECT_ROW2_EQ(11, 15, 19, 23, B);
    EXPECT_ROW3_EQ(12, 16, 20, 24, B);
    EXPECT_ROW4_EQ(13, 17, 21, 25, B);
  }
  
  TEST(WebTransformationMatrixTest, MatrixInversion) {
    // Invert a translation
    WebTransformationMatrix translation;
    translation.translate3d(2, 3, 4);
    EXPECT_TRUE(translation.isInvertible());
  
    WebTransformationMatrix inverse_translation = translation.inverse();
    EXPECT_ROW1_EQ(1, 0, 0, -2, inverse_translation);
    EXPECT_ROW2_EQ(0, 1, 0, -3, inverse_translation);
    EXPECT_ROW3_EQ(0, 0, 1, -4, inverse_translation);
    EXPECT_ROW4_EQ(0, 0, 0, 1, inverse_translation);
  
    // Note that inversion should not have changed the original matrix.
    EXPECT_ROW1_EQ(1, 0, 0, 2, translation);
    EXPECT_ROW2_EQ(0, 1, 0, 3, translation);
    EXPECT_ROW3_EQ(0, 0, 1, 4, translation);
    EXPECT_ROW4_EQ(0, 0, 0, 1, translation);
  
    // Invert a non-uniform scale
    WebTransformationMatrix scale;
    scale.scale3d(4, 10, 100);
    EXPECT_TRUE(scale.isInvertible());
  
    WebTransformationMatrix inverse_scale = scale.inverse();
    EXPECT_ROW1_EQ(0.25, 0, 0, 0, inverse_scale);
    EXPECT_ROW2_EQ(0, .1f, 0, 0, inverse_scale);
    EXPECT_ROW3_EQ(0, 0, .01f, 0, inverse_scale);
    EXPECT_ROW4_EQ(0, 0, 0, 1, inverse_scale);
  
    // Try to invert a matrix that is not invertible.
    // The inverse() function should simply return an identity matrix.
    WebTransformationMatrix not_invertible;
    not_invertible.setM11(0);
    not_invertible.setM22(0);
    not_invertible.setM33(0);
    not_invertible.setM44(0);
    EXPECT_FALSE(not_invertible.isInvertible());
  
    WebTransformationMatrix inverse_of_not_invertible;
    initializeTestMatrix(
        inverse_of_not_invertible);  // initialize this to something non-identity, to make sure that assignment below actually took place.
    inverse_of_not_invertible = not_invertible.inverse();
    EXPECT_TRUE(inverse_of_not_invertible.isIdentity());
  }
  
  TEST(WebTransformationMatrixTest, To2DTransform) {
    WebTransformationMatrix A;
    initializeTestMatrix(A);
  
    WebTransformationMatrix B = A.to2dTransform();
  
    EXPECT_ROW1_EQ(10, 14, 0, 22, B);
    EXPECT_ROW2_EQ(11, 15, 0, 23, B);
    EXPECT_ROW3_EQ(0, 0, 1, 0, B);
    EXPECT_ROW4_EQ(13, 17, 0, 25, B);
  
    // Note that to2DTransform should not have changed the original matrix.
    EXPECT_ROW1_EQ(10, 14, 18, 22, A);
    EXPECT_ROW2_EQ(11, 15, 19, 23, A);
    EXPECT_ROW3_EQ(12, 16, 20, 24, A);
    EXPECT_ROW4_EQ(13, 17, 21, 25, A);
  }
  
  TEST(WebTransformationMatrixTest, AssignmentOperator) {
    WebTransformationMatrix A;
    initializeTestMatrix(A);
    WebTransformationMatrix B;
    initializeTestMatrix2(B);
    WebTransformationMatrix C;
    initializeTestMatrix2(C);
    C = B = A;
  
    // Both B and C should now have been re-assigned to the value of A.
    EXPECT_ROW1_EQ(10, 14, 18, 22, B);
    EXPECT_ROW2_EQ(11, 15, 19, 23, B);
    EXPECT_ROW3_EQ(12, 16, 20, 24, B);
    EXPECT_ROW4_EQ(13, 17, 21, 25, B);
  
    EXPECT_ROW1_EQ(10, 14, 18, 22, C);
    EXPECT_ROW2_EQ(11, 15, 19, 23, C);
    EXPECT_ROW3_EQ(12, 16, 20, 24, C);
    EXPECT_ROW4_EQ(13, 17, 21, 25, C);
  }
  
  TEST(WebTransformationMatrixTest, EqualsBooleanOperator) {
    WebTransformationMatrix A;
    initializeTestMatrix(A);
  
    WebTransformationMatrix B;
    initializeTestMatrix(B);
    EXPECT_TRUE(A == B);
  
    // Modifying multiple elements should cause equals operator to return false.
    WebTransformationMatrix C;
    initializeTestMatrix2(C);
    EXPECT_FALSE(A == C);
  
    // Modifying any one individual element should cause equals operator to return false.
    WebTransformationMatrix D;
    D = A;
    D.setM11(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM12(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM13(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM14(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM21(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM22(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM23(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM24(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM31(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM32(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM33(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM34(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM41(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM42(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM43(0);
    EXPECT_FALSE(A == D);
  
    D = A;
    D.setM44(0);
    EXPECT_FALSE(A == D);
  }
  
  TEST(WebTransformationMatrixTest, MultiplyOperator) {
    WebTransformationMatrix A;
    initializeTestMatrix(A);
  
    WebTransformationMatrix B;
    initializeTestMatrix2(B);
  
    WebTransformationMatrix C = A * B;
    EXPECT_ROW1_EQ(2036, 2292, 2548, 2804, C);
    EXPECT_ROW2_EQ(2162, 2434, 2706, 2978, C);
    EXPECT_ROW3_EQ(2288, 2576, 2864, 3152, C);
    EXPECT_ROW4_EQ(2414, 2718, 3022, 3326, C);
  
    // Just an additional sanity check; matrix multiplication is not commutative.
    EXPECT_FALSE(A * B == B * A);
  }
  
  TEST(WebTransformationMatrixTest, MatrixMultiplication) {
    WebTransformationMatrix A;
    initializeTestMatrix(A);
  
    WebTransformationMatrix B;
    initializeTestMatrix2(B);
  
    A.multiply(B);
    EXPECT_ROW1_EQ(2036, 2292, 2548, 2804, A);
    EXPECT_ROW2_EQ(2162, 2434, 2706, 2978, A);
    EXPECT_ROW3_EQ(2288, 2576, 2864, 3152, A);
    EXPECT_ROW4_EQ(2414, 2718, 3022, 3326, A);
  }
  
  TEST(WebTransformationMatrixTest, MakeIdentiy) {
    WebTransformationMatrix A;
    initializeTestMatrix(A);
    A.makeIdentity();
    EXPECT_ROW1_EQ(1, 0, 0, 0, A);
    EXPECT_ROW2_EQ(0, 1, 0, 0, A);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
    EXPECT_TRUE(A.isIdentity());
  }
  
  TEST(WebTransformationMatrixTest, Translate) {
    WebTransformationMatrix A;
    A.translate(2, 3);
    EXPECT_ROW1_EQ(1, 0, 0, 2, A);
    EXPECT_ROW2_EQ(0, 1, 0, 3, A);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Verify that translate() post-multiplies the existing matrix.
    A.makeIdentity();
    A.scale(5);
    A.translate(2, 3);
    EXPECT_ROW1_EQ(5, 0, 0, 10, A);
    EXPECT_ROW2_EQ(0, 5, 0, 15, A);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, Translate3d) {
    WebTransformationMatrix A;
    A.translate3d(2, 3, 4);
    EXPECT_ROW1_EQ(1, 0, 0, 2, A);
    EXPECT_ROW2_EQ(0, 1, 0, 3, A);
    EXPECT_ROW3_EQ(0, 0, 1, 4, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Verify that translate3d() post-multiplies the existing matrix.
    A.makeIdentity();
    A.scale3d(6, 7, 8);
    A.translate3d(2, 3, 4);
    EXPECT_ROW1_EQ(6, 0, 0, 12, A);
    EXPECT_ROW2_EQ(0, 7, 0, 21, A);
    EXPECT_ROW3_EQ(0, 0, 8, 32, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, TranslateRight3d) {
    WebTransformationMatrix A;
    A.translateRight3d(2, 3, 4);
    EXPECT_ROW1_EQ(1, 0, 0, 2, A);
    EXPECT_ROW2_EQ(0, 1, 0, 3, A);
    EXPECT_ROW3_EQ(0, 0, 1, 4, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Note carefully, all other operations do post-multiply, this one is unique.
    // Verify that translateRight3d() PRE-multiplies the existing matrix.
    A.makeIdentity();
    A.scale3d(6, 7, 8);
    A.translateRight3d(2, 3, 4);
    EXPECT_ROW1_EQ(6, 0, 0, 2, A);
    EXPECT_ROW2_EQ(0, 7, 0, 3, A);
    EXPECT_ROW3_EQ(0, 0, 8, 4, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, Scale) {
    WebTransformationMatrix A;
    A.scale(5);
    EXPECT_ROW1_EQ(5, 0, 0, 0, A);
    EXPECT_ROW2_EQ(0, 5, 0, 0, A);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Verify that scale() post-multiplies the existing matrix.
    A.makeIdentity();
    A.translate3d(2, 3, 4);
    A.scale(5);
    EXPECT_ROW1_EQ(5, 0, 0, 2, A);
    EXPECT_ROW2_EQ(0, 5, 0, 3, A);
    EXPECT_ROW3_EQ(0, 0, 1, 4, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, NonUniformScale) {
    WebTransformationMatrix A;
    A.scaleNonUniform(6, 7);
    EXPECT_ROW1_EQ(6, 0, 0, 0, A);
    EXPECT_ROW2_EQ(0, 7, 0, 0, A);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Verify that scaleNonUniform() post-multiplies the existing matrix.
    A.makeIdentity();
    A.translate3d(2, 3, 4);
    A.scaleNonUniform(6, 7);
    EXPECT_ROW1_EQ(6, 0, 0, 2, A);
    EXPECT_ROW2_EQ(0, 7, 0, 3, A);
    EXPECT_ROW3_EQ(0, 0, 1, 4, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, Scale3d) {
    WebTransformationMatrix A;
    A.scale3d(6, 7, 8);
    EXPECT_ROW1_EQ(6, 0, 0, 0, A);
    EXPECT_ROW2_EQ(0, 7, 0, 0, A);
    EXPECT_ROW3_EQ(0, 0, 8, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Verify that scale3d() post-multiplies the existing matrix.
    A.makeIdentity();
    A.translate3d(2, 3, 4);
    A.scale3d(6, 7, 8);
    EXPECT_ROW1_EQ(6, 0, 0, 2, A);
    EXPECT_ROW2_EQ(0, 7, 0, 3, A);
    EXPECT_ROW3_EQ(0, 0, 8, 4, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, Rotate) {
    WebTransformationMatrix A;
    A.rotate(90);
    EXPECT_ROW1_NEAR(0, -1, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(1, 0, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Verify that rotate() post-multiplies the existing matrix.
    A.makeIdentity();
    A.scale3d(6, 7, 8);
    A.rotate(90);
    EXPECT_ROW1_NEAR(0, -6, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(7, 0, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW3_EQ(0, 0, 8, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, Rotate3d) {
    WebTransformationMatrix A;
  
    // Check rotation about z-axis
    A.makeIdentity();
    A.rotate3d(0, 0, 90);
    EXPECT_ROW1_NEAR(0, -1, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(1, 0, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Check rotation about x-axis
    A.makeIdentity();
    A.rotate3d(90, 0, 0);
    EXPECT_ROW1_EQ(1, 0, 0, 0, A);
    EXPECT_ROW2_NEAR(0, 0, -1, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(0, 1, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Check rotation about y-axis.
    // Note carefully, the expected pattern is inverted compared to rotating about x axis or z axis.
    A.makeIdentity();
    A.rotate3d(0, 90, 0);
    EXPECT_ROW1_NEAR(0, 0, 1, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW2_EQ(0, 1, 0, 0, A);
    EXPECT_ROW3_NEAR(-1, 0, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Verify that rotate3d(rx, ry, rz) post-multiplies the existing matrix.
    A.makeIdentity();
    A.scale3d(6, 7, 8);
    A.rotate3d(0, 0, 90);
    EXPECT_ROW1_NEAR(0, -6, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(7, 0, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW3_EQ(0, 0, 8, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, Rotate3dOrderOfCompositeRotations) {
    // Rotate3d(degreesX, degreesY, degreesZ) is actually composite transform consiting of
    // three primitive rotations. This test verifies that the ordering of those three
    // transforms is the intended ordering.
    //
    // The correct ordering for this test case should be:
    //   1. rotate by 30 degrees about z-axis
    //   2. rotate by 20 degrees about y-axis
    //   3. rotate by 10 degrees about x-axis
    //
    // Note: there are 6 possible orderings of 3 transforms. For the specific transforms
    // used in this test, all 6 combinations produce a unique matrix that is different
    // from the other orderings. That way, this test verifies the exact ordering.
  
    WebTransformationMatrix A;
    A.makeIdentity();
    A.rotate3d(10, 20, 30);
  
    EXPECT_ROW1_NEAR(0.8137976813493738026394908,
                     -0.4409696105298823720630708,
                     0.3785223063697923939763257,
                     0,
                     A,
                     ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(0.4698463103929541584413698,
                     0.8825641192593856043657752,
                     0.0180283112362972230968694,
                     0,
                     A,
                     ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(-0.3420201433256686573969318,
                     0.1631759111665348205288950,
                     0.9254165783983233639631294,
                     0,
                     A,
                     ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, RotateAxisAngle3d) {
    WebTransformationMatrix A;
  
    // Check rotation about z-axis
    A.makeIdentity();
    A.rotate3d(0, 0, 1, 90);
    EXPECT_ROW1_NEAR(0, -1, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(1, 0, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Check rotation about x-axis
    A.makeIdentity();
    A.rotate3d(1, 0, 0, 90);
    EXPECT_ROW1_EQ(1, 0, 0, 0, A);
    EXPECT_ROW2_NEAR(0, 0, -1, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(0, 1, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Check rotation about y-axis.
    // Note carefully, the expected pattern is inverted compared to rotating about x axis or z axis.
    A.makeIdentity();
    A.rotate3d(0, 1, 0, 90);
    EXPECT_ROW1_NEAR(0, 0, 1, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW2_EQ(0, 1, 0, 0, A);
    EXPECT_ROW3_NEAR(-1, 0, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Verify that rotate3d(axis, angle) post-multiplies the existing matrix.
    A.makeIdentity();
    A.scale3d(6, 7, 8);
    A.rotate3d(0, 0, 1, 90);
    EXPECT_ROW1_NEAR(0, -6, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(7, 0, 0, 0, A, ERROR_THRESHOLD);
    EXPECT_ROW3_EQ(0, 0, 8, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, RotateAxisAngle3dForArbitraryAxis) {
    // Check rotation about an arbitrary non-axis-aligned vector.
    WebTransformationMatrix A;
    A.rotate3d(1, 1, 1, 90);
    EXPECT_ROW1_NEAR(0.3333333333333334258519187,
                     -0.2440169358562924717404030,
                     0.9106836025229592124219380,
                     0,
                     A,
                     ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(0.9106836025229592124219380,
                     0.3333333333333334258519187,
                     -0.2440169358562924717404030,
                     0,
                     A,
                     ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(-0.2440169358562924717404030,
                     0.9106836025229592124219380,
                     0.3333333333333334258519187,
                     0,
                     A,
                     ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, RotateAxisAngle3dForDegenerateAxis) {
    // Check rotation about a degenerate zero vector.
    // It is expected to skip applying the rotation.
    WebTransformationMatrix A;
  
    A.rotate3d(0, 0, 0, 45);
    // Verify that A remains unchanged.
    EXPECT_TRUE(A.isIdentity());
  
    initializeTestMatrix(A);
    A.rotate3d(0, 0, 0, 35);
  
    // Verify that A remains unchanged.
    EXPECT_ROW1_EQ(10, 14, 18, 22, A);
    EXPECT_ROW2_EQ(11, 15, 19, 23, A);
    EXPECT_ROW3_EQ(12, 16, 20, 24, A);
    EXPECT_ROW4_EQ(13, 17, 21, 25, A);
  }
  
  TEST(WebTransformationMatrixTest, SkewX) {
    WebTransformationMatrix A;
    A.skewX(45);
    EXPECT_ROW1_EQ(1, 1, 0, 0, A);
    EXPECT_ROW2_EQ(0, 1, 0, 0, A);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Verify that skewX() post-multiplies the existing matrix.
    // Row 1, column 2, would incorrectly have value "7" if the matrix is pre-multiplied instead of post-multiplied.
    A.makeIdentity();
    A.scale3d(6, 7, 8);
    A.skewX(45);
    EXPECT_ROW1_EQ(6, 6, 0, 0, A);
    EXPECT_ROW2_EQ(0, 7, 0, 0, A);
    EXPECT_ROW3_EQ(0, 0, 8, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, SkewY) {
    WebTransformationMatrix A;
    A.skewY(45);
    EXPECT_ROW1_EQ(1, 0, 0, 0, A);
    EXPECT_ROW2_EQ(1, 1, 0, 0, A);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  
    // Verify that skewY() post-multiplies the existing matrix.
    // Row 2, column 1, would incorrectly have value "6" if the matrix is pre-multiplied instead of post-multiplied.
    A.makeIdentity();
    A.scale3d(6, 7, 8);
    A.skewY(45);
    EXPECT_ROW1_EQ(6, 0, 0, 0, A);
    EXPECT_ROW2_EQ(7, 7, 0, 0, A);
    EXPECT_ROW3_EQ(0, 0, 8, 0, A);
    EXPECT_ROW4_EQ(0, 0, 0, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, ApplyPerspective) {
    WebTransformationMatrix A;
    A.applyPerspective(1);
    EXPECT_ROW1_EQ(1, 0, 0, 0, A);
    EXPECT_ROW2_EQ(0, 1, 0, 0, A);
    EXPECT_ROW3_EQ(0, 0, 1, 0, A);
    EXPECT_ROW4_EQ(0, 0, -1, 1, A);
  
    // Verify that applyPerspective() post-multiplies the existing matrix.
    A.makeIdentity();
    A.translate3d(2, 3, 4);
    A.applyPerspective(1);
    EXPECT_ROW1_EQ(1, 0, -2, 2, A);
    EXPECT_ROW2_EQ(0, 1, -3, 3, A);
    EXPECT_ROW3_EQ(0, 0, -3, 4, A);
    EXPECT_ROW4_EQ(0, 0, -1, 1, A);
  }
  
  TEST(WebTransformationMatrixTest, HasPerspective) {
    WebTransformationMatrix A;
    A.applyPerspective(1);
    EXPECT_TRUE(A.hasPerspective());
  
    A.makeIdentity();
    A.applyPerspective(0);
    EXPECT_FALSE(A.hasPerspective());
  
    A.makeIdentity();
    A.setM34(-0.3);
    EXPECT_TRUE(A.hasPerspective());
  
    // FIXME: WebCore only checkes m34() for perspective, but that is probably
    //        wrong. https://bugs.webkit.org/show_bug.cgi?id=83088. For now, this test
    //        case expects the exact behavior as implemented by WebCore, but this should
    //        probably be changed so that if the entire bottom row is not exactly
    //        (0, 0, 0, 1), then hasPerspective should return true.
  
    A.makeIdentity();
    A.setM14(-1);
    EXPECT_FALSE(A.hasPerspective());
  
    A.makeIdentity();
    A.setM24(-1);
    EXPECT_FALSE(A.hasPerspective());
  
    A.makeIdentity();
    A.setM44(0.5);
    EXPECT_FALSE(A.hasPerspective());
  }
  
  TEST(WebTransformationMatrixTest, IsInvertible) {
    WebTransformationMatrix A;
  
    // Translations, rotations, scales, skews and arbitrary combinations of them are invertible.
    A.makeIdentity();
    EXPECT_TRUE(A.isInvertible());
  
    A.makeIdentity();
    A.translate3d(2, 3, 4);
    EXPECT_TRUE(A.isInvertible());
  
    A.makeIdentity();
    A.scale3d(6, 7, 8);
    EXPECT_TRUE(A.isInvertible());
  
    A.makeIdentity();
    A.rotate3d(10, 20, 30);
    EXPECT_TRUE(A.isInvertible());
  
    A.makeIdentity();
    A.skewX(45);
    EXPECT_TRUE(A.isInvertible());
  
    // A perspective matrix (projection plane at z=0) is invertible. The intuitive
    // explanation is that perspective is eqivalent to a skew of the w-axis; skews are
    // invertible.
    A.makeIdentity();
    A.applyPerspective(1);
    EXPECT_TRUE(A.isInvertible());
  
    // A "pure" perspective matrix derived by similar triangles, with m44() set to zero
    // (i.e. camera positioned at the origin), is not invertible.
    A.makeIdentity();
    A.applyPerspective(1);
    A.setM44(0);
    EXPECT_FALSE(A.isInvertible());
  
    // Adding more to a non-invertible matrix will not make it invertible in the general case.
    A.makeIdentity();
    A.applyPerspective(1);
    A.setM44(0);
    A.scale3d(6, 7, 8);
    A.rotate3d(10, 20, 30);
    A.translate3d(6, 7, 8);
    EXPECT_FALSE(A.isInvertible());
  
    // A degenerate matrix of all zeros is not invertible.
    A.makeIdentity();
    A.setM11(0);
    A.setM22(0);
    A.setM33(0);
    A.setM44(0);
    EXPECT_FALSE(A.isInvertible());
  }
  
  TEST(WebTransformationMatrixTest, IsIdentity) {
    WebTransformationMatrix A;
  
    initializeTestMatrix(A);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    EXPECT_TRUE(A.isIdentity());
  
    // Modifying any one individual element should cause the matrix to no longer be identity.
    A.makeIdentity();
    A.setM11(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM12(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM13(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM14(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM21(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM22(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM23(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM24(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM31(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM32(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM33(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM34(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM41(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM42(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM43(2);
    EXPECT_FALSE(A.isIdentity());
  
    A.makeIdentity();
    A.setM44(2);
    EXPECT_FALSE(A.isIdentity());
  }
  
  TEST(WebTransformationMatrixTest, IsIdentityOrTranslation) {
    WebTransformationMatrix A;
  
    initializeTestMatrix(A);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    EXPECT_TRUE(A.isIdentityOrTranslation());
  
    // Modifying any non-translation components should cause isIdentityOrTranslation() to
    // return false. NOTE: m41(), m42(), and m43() are the translation components, so
    // modifying them should still return true for isIdentityOrTranslation().
    A.makeIdentity();
    A.setM11(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    A.setM12(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    A.setM13(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    A.setM14(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    A.setM21(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    A.setM22(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    A.setM23(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    A.setM24(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    A.setM31(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    A.setM32(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    A.setM33(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    A.setM34(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  
    // Note carefully - expecting true here.
    A.makeIdentity();
    A.setM41(2);
    EXPECT_TRUE(A.isIdentityOrTranslation());
  
    // Note carefully - expecting true here.
    A.makeIdentity();
    A.setM42(2);
    EXPECT_TRUE(A.isIdentityOrTranslation());
  
    // Note carefully - expecting true here.
    A.makeIdentity();
    A.setM43(2);
    EXPECT_TRUE(A.isIdentityOrTranslation());
  
    A.makeIdentity();
    A.setM44(2);
    EXPECT_FALSE(A.isIdentityOrTranslation());
  }
  
  TEST(WebTransformationMatrixTest, IsIntegerTranslation) {
    WebTransformationMatrix A;
  
    A.makeIdentity();
    A.translate(2, 3);
    EXPECT_TRUE(A.isIntegerTranslation());
  
    A.makeIdentity();
    A.translate(2, 3);
    EXPECT_TRUE(A.isIntegerTranslation());
  
    A.makeIdentity();
    A.translate(2.00001, 3);
    EXPECT_FALSE(A.isIntegerTranslation());
  
    A.makeIdentity();
    A.translate(2, 2.99999);
    EXPECT_FALSE(A.isIntegerTranslation());
  
    // Stacking many integer translations should ideally not accumulate any precision error.
    A.makeIdentity();
    for (int i = 0; i < 100000; ++i)
      A.translate(2, 3);
    EXPECT_TRUE(A.isIntegerTranslation());
  }
  
  TEST(WebTransformationMatrixTest, BlendForTranslation) {
    WebTransformationMatrix from;
    from.translate3d(100, 200, 100);
  
    WebTransformationMatrix to;
  
    to.makeIdentity();
    to.translate3d(200, 100, 300);
    to.blend(from, 0);
    EXPECT_WEB_TRANSFORMATION_MATRIX_EQ(from, to);
  
    to.makeIdentity();
    to.translate3d(200, 100, 300);
    to.blend(from, 0.25);
    EXPECT_ROW1_EQ(1, 0, 0, 125, to);
    EXPECT_ROW2_EQ(0, 1, 0, 175, to);
    EXPECT_ROW3_EQ(0, 0, 1, 150, to);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    to.makeIdentity();
    to.translate3d(200, 100, 300);
    to.blend(from, 0.5);
    EXPECT_ROW1_EQ(1, 0, 0, 150, to);
    EXPECT_ROW2_EQ(0, 1, 0, 150, to);
    EXPECT_ROW3_EQ(0, 0, 1, 200, to);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    to.makeIdentity();
    to.translate3d(200, 100, 300);
    to.blend(from, 1);
    EXPECT_ROW1_EQ(1, 0, 0, 200, to);
    EXPECT_ROW2_EQ(0, 1, 0, 100, to);
    EXPECT_ROW3_EQ(0, 0, 1, 300, to);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  }
  
  TEST(WebTransformationMatrixTest, BlendForScale) {
    WebTransformationMatrix from;
    from.scale3d(100, 200, 100);
  
    WebTransformationMatrix to;
  
    to.makeIdentity();
    to.scale3d(200, 100, 300);
    to.blend(from, 0);
    EXPECT_WEB_TRANSFORMATION_MATRIX_EQ(from, to);
  
    to.makeIdentity();
    to.scale3d(200, 100, 300);
    to.blend(from, 0.25);
    EXPECT_ROW1_EQ(125, 0, 0, 0, to);
    EXPECT_ROW2_EQ(0, 175, 0, 0, to);
    EXPECT_ROW3_EQ(0, 0, 150, 0, to);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    to.makeIdentity();
    to.scale3d(200, 100, 300);
    to.blend(from, 0.5);
    EXPECT_ROW1_EQ(150, 0, 0, 0, to);
    EXPECT_ROW2_EQ(0, 150, 0, 0, to);
    EXPECT_ROW3_EQ(0, 0, 200, 0, to);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    to.makeIdentity();
    to.scale3d(200, 100, 300);
    to.blend(from, 1);
    EXPECT_ROW1_EQ(200, 0, 0, 0, to);
    EXPECT_ROW2_EQ(0, 100, 0, 0, to);
    EXPECT_ROW3_EQ(0, 0, 300, 0, to);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  }
  
  TEST(WebTransformationMatrixTest, BlendForSkewX) {
    WebTransformationMatrix from;
    from.skewX(0);
  
    WebTransformationMatrix to;
  
    to.makeIdentity();
    to.skewX(45);
    to.blend(from, 0);
    EXPECT_WEB_TRANSFORMATION_MATRIX_EQ(from, to);
  
    to.makeIdentity();
    to.skewX(45);
    to.blend(from, 0.5);
    EXPECT_ROW1_EQ(1, 0.5, 0, 0, to);
    EXPECT_ROW2_EQ(0, 1, 0, 0, to);
    EXPECT_ROW3_EQ(0, 0, 1, 0, to);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    to.makeIdentity();
    to.skewX(45);
    to.blend(from, 0.25);
    EXPECT_ROW1_EQ(1, 0.25, 0, 0, to);
    EXPECT_ROW2_EQ(0, 1, 0, 0, to);
    EXPECT_ROW3_EQ(0, 0, 1, 0, to);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    to.makeIdentity();
    to.skewX(45);
    to.blend(from, 1);
    EXPECT_ROW1_EQ(1, 1, 0, 0, to);
    EXPECT_ROW2_EQ(0, 1, 0, 0, to);
    EXPECT_ROW3_EQ(0, 0, 1, 0, to);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  }
  
  TEST(WebTransformationMatrixTest, BlendForSkewY) {
    // NOTE CAREFULLY: Decomposition of skew and rotation terms of the matrix is
    // inherently underconstrained, and so it does not always compute the originally
    // intended skew parameters. The current implementation uses QR decomposition, which
    // decomposes the shear into a rotation + non-uniform scale.
    //
    // It is unlikely that the decomposition implementation will need to change very
    // often, so to get any test coverage, the compromise is to verify the exact matrix
    // that the blend() operation produces.
    //
    // This problem also potentially exists for skewX, but the current QR decomposition
    // implementation just happens to decompose those test matrices intuitively.
  
    WebTransformationMatrix from;
    from.skewY(0);
  
    WebTransformationMatrix to;
  
    to.makeIdentity();
    to.skewY(45);
    to.blend(from, 0);
    EXPECT_WEB_TRANSFORMATION_MATRIX_EQ(from, to);
  
    to.makeIdentity();
    to.skewY(45);
    to.blend(from, 0.25);
    EXPECT_ROW1_NEAR(1.0823489449280947471976333,
                     0.0464370719145053845178239,
                     0,
                     0,
                     to,
                     LOOSE_ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(0.2152925909665224513123150,
                     0.9541702441750861130032035,
                     0,
                     0,
                     to,
                     LOOSE_ERROR_THRESHOLD);
    EXPECT_ROW3_EQ(0, 0, 1, 0, to);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    to.makeIdentity();
    to.skewY(45);
    to.blend(from, 0.5);
    EXPECT_ROW1_NEAR(1.1152212925809066312865525,
                     0.0676495144007326631996335,
                     0,
                     0,
                     to,
                     LOOSE_ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(0.4619397844342648662419037,
                     0.9519009045724774464858342,
                     0,
                     0,
                     to,
                     LOOSE_ERROR_THRESHOLD);
    EXPECT_ROW3_EQ(0, 0, 1, 0, to);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    // Unfortunately, this case suffers from uncomfortably large precision error.
    to.makeIdentity();
    to.skewY(45);
    to.blend(from, 1);
    EXPECT_ROW1_NEAR(1, 0, 0, 0, to, LOOSE_ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(1, 1, 0, 0, to, LOOSE_ERROR_THRESHOLD);
    EXPECT_ROW3_EQ(0, 0, 1, 0, to);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  }
  
  TEST(WebTransformationMatrixTest, BlendForRotationAboutX) {
    // Even though blending uses quaternions, axis-aligned rotations should blend the same
    // with quaternions or Euler angles. So we can test rotation blending by comparing
    // against manually specified matrices from Euler angles.
  
    WebTransformationMatrix from;
    from.rotate3d(1, 0, 0, 0);
  
    WebTransformationMatrix to;
  
    to.makeIdentity();
    to.rotate3d(1, 0, 0, 90);
    to.blend(from, 0);
    EXPECT_WEB_TRANSFORMATION_MATRIX_EQ(from, to);
  
    double expected_rotation_angle = 22.5 * M_PI / 180.0;
    to.makeIdentity();
    to.rotate3d(1, 0, 0, 90);
    to.blend(from, 0.25);
    EXPECT_ROW1_NEAR(1, 0, 0, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(0,
                     cos(expected_rotation_angle),
                     -sin(expected_rotation_angle),
                     0,
                     to,
                     ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(0,
                     sin(expected_rotation_angle),
                     cos(expected_rotation_angle),
                     0,
                     to,
                     ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    expected_rotation_angle = 45 * M_PI / 180.0;
    to.makeIdentity();
    to.rotate3d(1, 0, 0, 90);
    to.blend(from, 0.5);
    EXPECT_ROW1_NEAR(1, 0, 0, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(0,
                     cos(expected_rotation_angle),
                     -sin(expected_rotation_angle),
                     0,
                     to,
                     ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(0,
                     sin(expected_rotation_angle),
                     cos(expected_rotation_angle),
                     0,
                     to,
                     ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    to.makeIdentity();
    to.rotate3d(1, 0, 0, 90);
    to.blend(from, 1);
    EXPECT_ROW1_NEAR(1, 0, 0, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(0, 0, -1, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(0, 1, 0, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  }
  
  TEST(WebTransformationMatrixTest, BlendForRotationAboutY) {
    WebTransformationMatrix from;
    from.rotate3d(0, 1, 0, 0);
  
    WebTransformationMatrix to;
  
    to.makeIdentity();
    to.rotate3d(0, 1, 0, 90);
    to.blend(from, 0);
    EXPECT_WEB_TRANSFORMATION_MATRIX_EQ(from, to);
  
    double expected_rotation_angle = 22.5 * M_PI / 180.0;
    to.makeIdentity();
    to.rotate3d(0, 1, 0, 90);
    to.blend(from, 0.25);
    EXPECT_ROW1_NEAR(cos(expected_rotation_angle),
                     0,
                     sin(expected_rotation_angle),
                     0,
                     to,
                     ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(0, 1, 0, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(-sin(expected_rotation_angle),
                     0,
                     cos(expected_rotation_angle),
                     0,
                     to,
                     ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    expected_rotation_angle = 45 * M_PI / 180.0;
    to.makeIdentity();
    to.rotate3d(0, 1, 0, 90);
    to.blend(from, 0.5);
    EXPECT_ROW1_NEAR(cos(expected_rotation_angle),
                     0,
                     sin(expected_rotation_angle),
                     0,
                     to,
                     ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(0, 1, 0, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(-sin(expected_rotation_angle),
                     0,
                     cos(expected_rotation_angle),
                     0,
                     to,
                     ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    to.makeIdentity();
    to.rotate3d(0, 1, 0, 90);
    to.blend(from, 1);
    EXPECT_ROW1_NEAR(0, 0, 1, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(0, 1, 0, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(-1, 0, 0, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  }
  
  TEST(WebTransformationMatrixTest, BlendForRotationAboutZ) {
    WebTransformationMatrix from;
    from.rotate3d(0, 0, 1, 0);
  
    WebTransformationMatrix to;
  
    to.makeIdentity();
    to.rotate3d(0, 0, 1, 90);
    to.blend(from, 0);
    EXPECT_WEB_TRANSFORMATION_MATRIX_EQ(from, to);
  
    double expected_rotation_angle = 22.5 * M_PI / 180.0;
    to.makeIdentity();
    to.rotate3d(0, 0, 1, 90);
    to.blend(from, 0.25);
    EXPECT_ROW1_NEAR(cos(expected_rotation_angle),
                     -sin(expected_rotation_angle),
                     0,
                     0,
                     to,
                     ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(sin(expected_rotation_angle),
                     cos(expected_rotation_angle),
                     0,
                     0,
                     to,
                     ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(0, 0, 1, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    expected_rotation_angle = 45 * M_PI / 180.0;
    to.makeIdentity();
    to.rotate3d(0, 0, 1, 90);
    to.blend(from, 0.5);
    EXPECT_ROW1_NEAR(cos(expected_rotation_angle),
                     -sin(expected_rotation_angle),
                     0,
                     0,
                     to,
                     ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(sin(expected_rotation_angle),
                     cos(expected_rotation_angle),
                     0,
                     0,
                     to,
                     ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(0, 0, 1, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  
    to.makeIdentity();
    to.rotate3d(0, 0, 1, 90);
    to.blend(from, 1);
    EXPECT_ROW1_NEAR(0, -1, 0, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW2_NEAR(1, 0, 0, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW3_NEAR(0, 0, 1, 0, to, ERROR_THRESHOLD);
    EXPECT_ROW4_EQ(0, 0, 0, 1, to);
  }
  
  TEST(WebTransformationMatrixTest, BlendForCompositeTransform) {
    // Verify that the blending was done with a decomposition in correct order by blending
    // a composite transform.
    // Using matrix x vector notation (Ax = b, where x is column vector), the ordering should be:
    // perspective * translation * rotation * skew * scale
    //
    // It is not as important (or meaningful) to check intermediate interpolations; order
    // of operations will be tested well enough by the end cases that are easier to
    // specify.
  
    WebTransformationMatrix from;
    WebTransformationMatrix to;
  
    WebTransformationMatrix expected_end_of_animation;
    expected_end_of_animation.applyPerspective(1);
    expected_end_of_animation.translate3d(10, 20, 30);
    expected_end_of_animation.rotate3d(0, 0, 1, 25);
    expected_end_of_animation.skewY(45);
    expected_end_of_animation.scale3d(6, 7, 8);
  
    to = expected_end_of_animation;
    to.blend(from, 0);
    EXPECT_WEB_TRANSFORMATION_MATRIX_EQ(from, to);
  
    to = expected_end_of_animation;
    to.blend(from, 1);
  
    // Recomposing the matrix results in a normalized matrix, so to verify we need to
    // normalize the expected_end_of_animation before comparing elements. Normalizing means
    // dividing everything by expected_end_of_animation.m44().
    WebTransformationMatrix normalized_expected_end_of_animation =
        expected_end_of_animation;
    WebTransformationMatrix normalization_matrix;
    normalization_matrix.setM11(1 / expected_end_of_animation.m44());
    normalization_matrix.setM22(1 / expected_end_of_animation.m44());
    normalization_matrix.setM33(1 / expected_end_of_animation.m44());
    normalization_matrix.setM44(1 / expected_end_of_animation.m44());
    normalized_expected_end_of_animation.multiply(normalization_matrix);
  
    EXPECT_WEB_TRANSFORMATION_MATRIX_EQ(normalized_expected_end_of_animation, to);
  }
  
  }  // namespace
  