diff options
author | shawnsingh@chromium.org <shawnsingh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-19 19:32:25 +0000 |
---|---|---|
committer | shawnsingh@chromium.org <shawnsingh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-19 19:32:25 +0000 |
commit | 3d9a2b650fadc91c01dc3861b6c552cf9ca270f1 (patch) | |
tree | b5b18cadfae4a216edfda0a30ed77623cd97fac3 /cc/math_util_unittest.cc | |
parent | 8bdbb0f49784d719c1e76e2fb8f119ef81ac5a60 (diff) | |
download | chromium_src-3d9a2b650fadc91c01dc3861b6c552cf9ca270f1.zip chromium_src-3d9a2b650fadc91c01dc3861b6c552cf9ca270f1.tar.gz chromium_src-3d9a2b650fadc91c01dc3861b6c552cf9ca270f1.tar.bz2 |
Implement unit tests and temporary MathUtil wrappers for transform functionality
These unit tests and wrappers have a short-term home in MathUtil so that we can
safely migrate from WebTransformationMatrix to gfx::Transform in a follow-up
patch. After the migration, we will then proceed to phase out this temporary
code and beef up ui/gfx/transform.h nicely.
BUG=159972
Review URL: https://codereview.chromium.org/11316043
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@168571 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'cc/math_util_unittest.cc')
-rw-r--r-- | cc/math_util_unittest.cc | 909 |
1 files changed, 909 insertions, 0 deletions
diff --git a/cc/math_util_unittest.cc b/cc/math_util_unittest.cc index c29bb12..93a980f 100644 --- a/cc/math_util_unittest.cc +++ b/cc/math_util_unittest.cc @@ -180,5 +180,914 @@ TEST(MathUtilTest, vectorProjection) projectedVector.y() / targetVector.y()); } +// TODO(shawnsingh): these macros are redundant with those from +// web_transformation_matrix_unittests, but for now they +// are different enough to be appropriate here. + +#define EXPECT_ROW1_EQ(a, b, c, d, transform) \ + EXPECT_FLOAT_EQ((a), (transform).matrix().getDouble(0, 0)); \ + EXPECT_FLOAT_EQ((b), (transform).matrix().getDouble(0, 1)); \ + EXPECT_FLOAT_EQ((c), (transform).matrix().getDouble(0, 2)); \ + EXPECT_FLOAT_EQ((d), (transform).matrix().getDouble(0, 3)); + +#define EXPECT_ROW2_EQ(a, b, c, d, transform) \ + EXPECT_FLOAT_EQ((a), (transform).matrix().getDouble(1, 0)); \ + EXPECT_FLOAT_EQ((b), (transform).matrix().getDouble(1, 1)); \ + EXPECT_FLOAT_EQ((c), (transform).matrix().getDouble(1, 2)); \ + EXPECT_FLOAT_EQ((d), (transform).matrix().getDouble(1, 3)); + +#define EXPECT_ROW3_EQ(a, b, c, d, transform) \ + EXPECT_FLOAT_EQ((a), (transform).matrix().getDouble(2, 0)); \ + EXPECT_FLOAT_EQ((b), (transform).matrix().getDouble(2, 1)); \ + EXPECT_FLOAT_EQ((c), (transform).matrix().getDouble(2, 2)); \ + EXPECT_FLOAT_EQ((d), (transform).matrix().getDouble(2, 3)); + +#define EXPECT_ROW4_EQ(a, b, c, d, transform) \ + EXPECT_FLOAT_EQ((a), (transform).matrix().getDouble(3, 0)); \ + EXPECT_FLOAT_EQ((b), (transform).matrix().getDouble(3, 1)); \ + EXPECT_FLOAT_EQ((c), (transform).matrix().getDouble(3, 2)); \ + EXPECT_FLOAT_EQ((d), (transform).matrix().getDouble(3, 3)); + +// 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, transform, errorThreshold) \ + EXPECT_NEAR((a), (transform).matrix().getDouble(0, 0), (errorThreshold)); \ + EXPECT_NEAR((b), (transform).matrix().getDouble(0, 1), (errorThreshold)); \ + EXPECT_NEAR((c), (transform).matrix().getDouble(0, 2), (errorThreshold)); \ + EXPECT_NEAR((d), (transform).matrix().getDouble(0, 3), (errorThreshold)); + +#define EXPECT_ROW2_NEAR(a, b, c, d, transform, errorThreshold) \ + EXPECT_NEAR((a), (transform).matrix().getDouble(1, 0), (errorThreshold)); \ + EXPECT_NEAR((b), (transform).matrix().getDouble(1, 1), (errorThreshold)); \ + EXPECT_NEAR((c), (transform).matrix().getDouble(1, 2), (errorThreshold)); \ + EXPECT_NEAR((d), (transform).matrix().getDouble(1, 3), (errorThreshold)); + +#define EXPECT_ROW3_NEAR(a, b, c, d, transform, errorThreshold) \ + EXPECT_NEAR((a), (transform).matrix().getDouble(2, 0), (errorThreshold)); \ + EXPECT_NEAR((b), (transform).matrix().getDouble(2, 1), (errorThreshold)); \ + EXPECT_NEAR((c), (transform).matrix().getDouble(2, 2), (errorThreshold)); \ + EXPECT_NEAR((d), (transform).matrix().getDouble(2, 3), (errorThreshold)); + +#define ERROR_THRESHOLD 1e-14 +#define LOOSE_ERROR_THRESHOLD 1e-7 + +static void initializeTestMatrix(gfx::Transform* transform) +{ + SkMatrix44& matrix = transform->matrix(); + matrix.setDouble(0, 0, 10); + matrix.setDouble(1, 0, 11); + matrix.setDouble(2, 0, 12); + matrix.setDouble(3, 0, 13); + matrix.setDouble(0, 1, 14); + matrix.setDouble(1, 1, 15); + matrix.setDouble(2, 1, 16); + matrix.setDouble(3, 1, 17); + matrix.setDouble(0, 2, 18); + matrix.setDouble(1, 2, 19); + matrix.setDouble(2, 2, 20); + matrix.setDouble(3, 2, 21); + matrix.setDouble(0, 3, 22); + matrix.setDouble(1, 3, 23); + matrix.setDouble(2, 3, 24); + matrix.setDouble(3, 3, 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(gfx::Transform* transform) +{ + SkMatrix44& matrix = transform->matrix(); + matrix.setDouble(0, 0, 30); + matrix.setDouble(1, 0, 31); + matrix.setDouble(2, 0, 32); + matrix.setDouble(3, 0, 33); + matrix.setDouble(0, 1, 34); + matrix.setDouble(1, 1, 35); + matrix.setDouble(2, 1, 36); + matrix.setDouble(3, 1, 37); + matrix.setDouble(0, 2, 38); + matrix.setDouble(1, 2, 39); + matrix.setDouble(2, 2, 40); + matrix.setDouble(3, 2, 41); + matrix.setDouble(0, 3, 42); + matrix.setDouble(1, 3, 43); + matrix.setDouble(2, 3, 44); + matrix.setDouble(3, 3, 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(MathUtilGfxTransformTest, verifyDefaultConstructorCreatesIdentityMatrix) +{ + gfx::Transform 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(MathUtil::isIdentity(A)); +} + +TEST(MathUtilGfxTransformTest, verifyCreateGfxTransformFor2dElements) +{ + gfx::Transform A = MathUtil::createGfxTransform(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(MathUtilGfxTransformTest, verifyCreateGfxTransformForAllElements) +{ + gfx::Transform A = MathUtil::createGfxTransform(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(MathUtilGfxTransformTest, verifyCopyConstructor) +{ + gfx::Transform A; + initializeTestMatrix(&A); + + // Copy constructor should produce exact same elements as matrix A. + gfx::Transform 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(MathUtilGfxTransformTest, verifyMatrixInversion) +{ + // Invert a translation + gfx::Transform translation; + translation.PreconcatTranslate3d(2, 3, 4); + EXPECT_TRUE(MathUtil::isInvertible(translation)); + + gfx::Transform inverseTranslation = MathUtil::inverse(translation); + EXPECT_ROW1_EQ(1, 0, 0, -2, inverseTranslation); + EXPECT_ROW2_EQ(0, 1, 0, -3, inverseTranslation); + EXPECT_ROW3_EQ(0, 0, 1, -4, inverseTranslation); + EXPECT_ROW4_EQ(0, 0, 0, 1, inverseTranslation); + + // 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 + gfx::Transform scale; + scale.PreconcatScale3d(4, 10, 100); + EXPECT_TRUE(MathUtil::isInvertible(scale)); + + gfx::Transform inverseScale = MathUtil::inverse(scale); + EXPECT_ROW1_EQ(0.25, 0, 0, 0, inverseScale); + EXPECT_ROW2_EQ(0, .1f, 0, 0, inverseScale); + EXPECT_ROW3_EQ(0, 0, .01f, 0, inverseScale); + EXPECT_ROW4_EQ(0, 0, 0, 1, inverseScale); + + // Try to invert a matrix that is not invertible. + // The inverse() function should simply return an identity matrix. + gfx::Transform notInvertible; + notInvertible.matrix().setDouble(0, 0, 0); + notInvertible.matrix().setDouble(1, 1, 0); + notInvertible.matrix().setDouble(2, 2, 0); + notInvertible.matrix().setDouble(3, 3, 0); + EXPECT_FALSE(MathUtil::isInvertible(notInvertible)); + + gfx::Transform inverseOfNotInvertible; + initializeTestMatrix(&inverseOfNotInvertible); // initialize this to something non-identity, to make sure that assignment below actually took place. + inverseOfNotInvertible = MathUtil::inverse(notInvertible); + EXPECT_TRUE(MathUtil::isIdentity(inverseOfNotInvertible)); +} + +TEST(MathUtilGfxTransformTest, verifyTo2DTransform) +{ + gfx::Transform A; + initializeTestMatrix(&A); + + gfx::Transform B = MathUtil::to2dTransform(A); + + 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(MathUtilGfxTransformTest, verifyAssignmentOperator) +{ + gfx::Transform A; + initializeTestMatrix(&A); + gfx::Transform B; + initializeTestMatrix2(&B); + gfx::Transform 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(MathUtilGfxTransformTest, verifyEqualsBooleanOperator) +{ + gfx::Transform A; + initializeTestMatrix(&A); + + gfx::Transform B; + initializeTestMatrix(&B); + EXPECT_TRUE(A == B); + + // Modifying multiple elements should cause equals operator to return false. + gfx::Transform C; + initializeTestMatrix2(&C); + EXPECT_FALSE(A == C); + + // Modifying any one individual element should cause equals operator to return false. + gfx::Transform D; + D = A; + D.matrix().setDouble(0, 0, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(1, 0, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(2, 0, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(3, 0, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(0, 1, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(1, 1, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(2, 1, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(3, 1, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(0, 2, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(1, 2, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(2, 2, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(3, 2, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(0, 3, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(1, 3, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(2, 3, 0); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().setDouble(3, 3, 0); + EXPECT_FALSE(A == D); +} + +TEST(MathUtilGfxTransformTest, verifyMultiplyOperator) +{ + gfx::Transform A; + initializeTestMatrix(&A); + + gfx::Transform B; + initializeTestMatrix2(&B); + + gfx::Transform 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(MathUtilGfxTransformTest, verifyMatrixMultiplication) +{ + gfx::Transform A; + initializeTestMatrix(&A); + + gfx::Transform B; + initializeTestMatrix2(&B); + + A.PreconcatTransform(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(MathUtilGfxTransformTest, verifyMakeIdentiy) +{ + gfx::Transform A; + initializeTestMatrix(&A); + MathUtil::makeIdentity(&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(MathUtil::isIdentity(A)); +} + +TEST(MathUtilGfxTransformTest, verifyTranslate) +{ + gfx::Transform A; + A.PreconcatTranslate(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 PreconcatTranslate() post-multiplies the existing matrix. + MathUtil::makeIdentity(&A); + A.PreconcatScale(5, 5); + A.PreconcatTranslate(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(MathUtilGfxTransformTest, verifyTranslate3d) +{ + gfx::Transform A; + A.PreconcatTranslate3d(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 PreconcatTranslate3d() post-multiplies the existing matrix. + MathUtil::makeIdentity(&A); + A.PreconcatScale3d(6, 7, 8); + A.PreconcatTranslate3d(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(MathUtilGfxTransformTest, verifyScale) +{ + gfx::Transform A; + A.PreconcatScale(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 PreconcatScale() post-multiplies the existing matrix. + MathUtil::makeIdentity(&A); + A.PreconcatTranslate3d(2, 3, 4); + A.PreconcatScale(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(MathUtilGfxTransformTest, verifyScale3d) +{ + gfx::Transform A; + A.PreconcatScale3d(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. + MathUtil::makeIdentity(&A); + A.PreconcatTranslate3d(2, 3, 4); + A.PreconcatScale3d(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(MathUtilGfxTransformTest, verifyRotate) +{ + gfx::Transform A; + A.PreconcatRotate(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 PreconcatRotate() post-multiplies the existing matrix. + MathUtil::makeIdentity(&A); + A.PreconcatScale3d(6, 7, 8); + A.PreconcatRotate(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(MathUtilGfxTransformTest, verifyRotateEulerAngles) +{ + gfx::Transform A; + + // Check rotation about z-axis + MathUtil::makeIdentity(&A); + MathUtil::rotateEulerAngles(&A, 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 + MathUtil::makeIdentity(&A); + MathUtil::rotateEulerAngles(&A, 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. + MathUtil::makeIdentity(&A); + MathUtil::rotateEulerAngles(&A, 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. + MathUtil::makeIdentity(&A); + A.PreconcatScale3d(6, 7, 8); + MathUtil::rotateEulerAngles(&A, 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(MathUtilGfxTransformTest, verifyRotateEulerAnglesOrderOfCompositeRotations) +{ + // 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. + + gfx::Transform A; + MathUtil::makeIdentity(&A); + MathUtil::rotateEulerAngles(&A, 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(MathUtilGfxTransformTest, verifyRotateAxisAngleForAlignedAxes) +{ + gfx::Transform A; + + // Check rotation about z-axis + MathUtil::makeIdentity(&A); + MathUtil::rotateAxisAngle(&A, 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 + MathUtil::makeIdentity(&A); + MathUtil::rotateAxisAngle(&A, 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. + MathUtil::makeIdentity(&A); + MathUtil::rotateAxisAngle(&A, 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. + MathUtil::makeIdentity(&A); + A.PreconcatScale3d(6, 7, 8); + MathUtil::rotateAxisAngle(&A, 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(MathUtilGfxTransformTest, verifyRotateAxisAngleForArbitraryAxis) +{ + // Check rotation about an arbitrary non-axis-aligned vector. + gfx::Transform A; + MathUtil::rotateAxisAngle(&A, 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(MathUtilGfxTransformTest, verifyRotateAxisAngleForDegenerateAxis) +{ + // Check rotation about a degenerate zero vector. + // It is expected to skip applying the rotation. + gfx::Transform A; + + MathUtil::rotateAxisAngle(&A, 0, 0, 0, 45); + // Verify that A remains unchanged. + EXPECT_TRUE(MathUtil::isIdentity(A)); + + initializeTestMatrix(&A); + MathUtil::rotateAxisAngle(&A, 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(MathUtilGfxTransformTest, verifySkewX) +{ + gfx::Transform A; + A.PreconcatSkewX(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. + MathUtil::makeIdentity(&A); + A.PreconcatScale3d(6, 7, 8); + A.PreconcatSkewX(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(MathUtilGfxTransformTest, verifySkewY) +{ + gfx::Transform A; + A.PreconcatSkewY(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. + MathUtil::makeIdentity(&A); + A.PreconcatScale3d(6, 7, 8); + A.PreconcatSkewY(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(MathUtilGfxTransformTest, verifyPerspectiveDepth) +{ + gfx::Transform A; + A.PreconcatPerspectiveDepth(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 PreconcatPerspectiveDepth() post-multiplies the existing matrix. + MathUtil::makeIdentity(&A); + A.PreconcatTranslate3d(2, 3, 4); + A.PreconcatPerspectiveDepth(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(MathUtilGfxTransformTest, verifyHasPerspective) +{ + gfx::Transform A; + A.PreconcatPerspectiveDepth(1); + EXPECT_TRUE(MathUtil::hasPerspective(A)); + + MathUtil::makeIdentity(&A); + A.PreconcatPerspectiveDepth(0); + EXPECT_FALSE(MathUtil::hasPerspective(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 0, -1); + EXPECT_TRUE(MathUtil::hasPerspective(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 1, -1); + EXPECT_TRUE(MathUtil::hasPerspective(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 2, -0.3); + EXPECT_TRUE(MathUtil::hasPerspective(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 3, 0.5); + EXPECT_TRUE(MathUtil::hasPerspective(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 3, 0); + EXPECT_TRUE(MathUtil::hasPerspective(A)); +} + +TEST(MathUtilGfxTransformTest, verifyIsInvertible) +{ + gfx::Transform A; + + // Translations, rotations, scales, skews and arbitrary combinations of them are invertible. + MathUtil::makeIdentity(&A); + EXPECT_TRUE(MathUtil::isInvertible(A)); + + MathUtil::makeIdentity(&A); + A.PreconcatTranslate3d(2, 3, 4); + EXPECT_TRUE(MathUtil::isInvertible(A)); + + MathUtil::makeIdentity(&A); + A.PreconcatScale3d(6, 7, 8); + EXPECT_TRUE(MathUtil::isInvertible(A)); + + MathUtil::makeIdentity(&A); + MathUtil::rotateEulerAngles(&A, 10, 20, 30); + EXPECT_TRUE(MathUtil::isInvertible(A)); + + MathUtil::makeIdentity(&A); + A.PreconcatSkewX(45); + EXPECT_TRUE(MathUtil::isInvertible(A)); + + // 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. + MathUtil::makeIdentity(&A); + A.PreconcatPerspectiveDepth(1); + EXPECT_TRUE(MathUtil::isInvertible(A)); + + // A "pure" perspective matrix derived by similar triangles, with m44() set to zero + // (i.e. camera positioned at the origin), is not invertible. + MathUtil::makeIdentity(&A); + A.PreconcatPerspectiveDepth(1); + A.matrix().setDouble(3, 3, 0); + EXPECT_FALSE(MathUtil::isInvertible(A)); + + // Adding more to a non-invertible matrix will not make it invertible in the general case. + MathUtil::makeIdentity(&A); + A.PreconcatPerspectiveDepth(1); + A.matrix().setDouble(3, 3, 0); + A.PreconcatScale3d(6, 7, 8); + MathUtil::rotateEulerAngles(&A, 10, 20, 30); + A.PreconcatTranslate3d(6, 7, 8); + EXPECT_FALSE(MathUtil::isInvertible(A)); + + // A degenerate matrix of all zeros is not invertible. + MathUtil::makeIdentity(&A); + A.matrix().setDouble(0, 0, 0); + A.matrix().setDouble(1, 1, 0); + A.matrix().setDouble(2, 2, 0); + A.matrix().setDouble(3, 3, 0); + EXPECT_FALSE(MathUtil::isInvertible(A)); +} + +TEST(MathUtilGfxTransformTest, verifyIsIdentity) +{ + gfx::Transform A; + + initializeTestMatrix(&A); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + EXPECT_TRUE(MathUtil::isIdentity(A)); + + // Modifying any one individual element should cause the matrix to no longer be identity. + MathUtil::makeIdentity(&A); + A.matrix().setDouble(0, 0, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(1, 0, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(2, 0, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 0, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(0, 1, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(1, 1, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(2, 1, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 1, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(0, 2, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(1, 2, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(2, 2, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 2, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(0, 3, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(1, 3, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(2, 3, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 3, 2); + EXPECT_FALSE(MathUtil::isIdentity(A)); +} + +TEST(MathUtilGfxTransformTest, verifyIsIdentityOrTranslation) +{ + gfx::Transform A; + + initializeTestMatrix(&A); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + EXPECT_TRUE(MathUtil::isIdentityOrTranslation(A)); + + // Modifying any non-translation components should cause isIdentityOrTranslation() to + // return false. NOTE: (0, 3), (1, 3), and (2, 3) are the translation components, so + // modifying them should still return true for isIdentityOrTranslation(). + MathUtil::makeIdentity(&A); + A.matrix().setDouble(0, 0, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(1, 0, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(2, 0, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 0, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(0, 0, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(1, 1, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(2, 1, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 1, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(0, 2, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(1, 2, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(2, 2, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 2, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); + + // Note carefully - expecting true here. + MathUtil::makeIdentity(&A); + A.matrix().setDouble(0, 3, 2); + EXPECT_TRUE(MathUtil::isIdentityOrTranslation(A)); + + // Note carefully - expecting true here. + MathUtil::makeIdentity(&A); + A.matrix().setDouble(1, 3, 2); + EXPECT_TRUE(MathUtil::isIdentityOrTranslation(A)); + + // Note carefully - expecting true here. + MathUtil::makeIdentity(&A); + A.matrix().setDouble(2, 3, 2); + EXPECT_TRUE(MathUtil::isIdentityOrTranslation(A)); + + MathUtil::makeIdentity(&A); + A.matrix().setDouble(3, 3, 2); + EXPECT_FALSE(MathUtil::isIdentityOrTranslation(A)); +} + } // namespace } // namespace cc |