|  | // Copyright (c) 2011 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. | 
|  |  | 
|  | // MSVC++ requires this to be set before any other includes to get M_PI. | 
|  | #define _USE_MATH_DEFINES | 
|  |  | 
|  | #include "ui/gfx/transform.h" | 
|  |  | 
|  | #include <cmath> | 
|  | #include <ostream> | 
|  | #include <limits> | 
|  |  | 
|  | #include "base/basictypes.h" | 
|  | #include "base/logging.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "ui/gfx/box_f.h" | 
|  | #include "ui/gfx/point.h" | 
|  | #include "ui/gfx/point3_f.h" | 
|  | #include "ui/gfx/quad_f.h" | 
|  | #include "ui/gfx/transform_util.h" | 
|  | #include "ui/gfx/vector3d_f.h" | 
|  |  | 
|  | namespace gfx { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | #define EXPECT_ROW1_EQ(a, b, c, d, transform)               \ | 
|  | EXPECT_FLOAT_EQ((a), (transform).matrix().get(0, 0));   \ | 
|  | EXPECT_FLOAT_EQ((b), (transform).matrix().get(0, 1));   \ | 
|  | EXPECT_FLOAT_EQ((c), (transform).matrix().get(0, 2));   \ | 
|  | EXPECT_FLOAT_EQ((d), (transform).matrix().get(0, 3)); | 
|  |  | 
|  | #define EXPECT_ROW2_EQ(a, b, c, d, transform)               \ | 
|  | EXPECT_FLOAT_EQ((a), (transform).matrix().get(1, 0));   \ | 
|  | EXPECT_FLOAT_EQ((b), (transform).matrix().get(1, 1));   \ | 
|  | EXPECT_FLOAT_EQ((c), (transform).matrix().get(1, 2));   \ | 
|  | EXPECT_FLOAT_EQ((d), (transform).matrix().get(1, 3)); | 
|  |  | 
|  | #define EXPECT_ROW3_EQ(a, b, c, d, transform)               \ | 
|  | EXPECT_FLOAT_EQ((a), (transform).matrix().get(2, 0));   \ | 
|  | EXPECT_FLOAT_EQ((b), (transform).matrix().get(2, 1));   \ | 
|  | EXPECT_FLOAT_EQ((c), (transform).matrix().get(2, 2));   \ | 
|  | EXPECT_FLOAT_EQ((d), (transform).matrix().get(2, 3)); | 
|  |  | 
|  | #define EXPECT_ROW4_EQ(a, b, c, d, transform)               \ | 
|  | EXPECT_FLOAT_EQ((a), (transform).matrix().get(3, 0));   \ | 
|  | EXPECT_FLOAT_EQ((b), (transform).matrix().get(3, 1));   \ | 
|  | EXPECT_FLOAT_EQ((c), (transform).matrix().get(3, 2));   \ | 
|  | EXPECT_FLOAT_EQ((d), (transform).matrix().get(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().get(0, 0), (errorThreshold)); \ | 
|  | EXPECT_NEAR((b), (transform).matrix().get(0, 1), (errorThreshold)); \ | 
|  | EXPECT_NEAR((c), (transform).matrix().get(0, 2), (errorThreshold)); \ | 
|  | EXPECT_NEAR((d), (transform).matrix().get(0, 3), (errorThreshold)); | 
|  |  | 
|  | #define EXPECT_ROW2_NEAR(a, b, c, d, transform, errorThreshold)         \ | 
|  | EXPECT_NEAR((a), (transform).matrix().get(1, 0), (errorThreshold)); \ | 
|  | EXPECT_NEAR((b), (transform).matrix().get(1, 1), (errorThreshold)); \ | 
|  | EXPECT_NEAR((c), (transform).matrix().get(1, 2), (errorThreshold)); \ | 
|  | EXPECT_NEAR((d), (transform).matrix().get(1, 3), (errorThreshold)); | 
|  |  | 
|  | #define EXPECT_ROW3_NEAR(a, b, c, d, transform, errorThreshold)         \ | 
|  | EXPECT_NEAR((a), (transform).matrix().get(2, 0), (errorThreshold)); \ | 
|  | EXPECT_NEAR((b), (transform).matrix().get(2, 1), (errorThreshold)); \ | 
|  | EXPECT_NEAR((c), (transform).matrix().get(2, 2), (errorThreshold)); \ | 
|  | EXPECT_NEAR((d), (transform).matrix().get(2, 3), (errorThreshold)); | 
|  |  | 
|  | bool PointsAreNearlyEqual(const Point3F& lhs, | 
|  | const Point3F& rhs) { | 
|  | float epsilon = 0.0001f; | 
|  | return lhs.SquaredDistanceTo(rhs) < epsilon; | 
|  | } | 
|  |  | 
|  | bool MatricesAreNearlyEqual(const Transform& lhs, | 
|  | const Transform& rhs) { | 
|  | float epsilon = 0.0001f; | 
|  | for (int row = 0; row < 4; ++row) { | 
|  | for (int col = 0; col < 4; ++col) { | 
|  | if (std::abs(lhs.matrix().get(row, col) - | 
|  | rhs.matrix().get(row, col)) > epsilon) | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void InitializeTestMatrix(Transform* transform) { | 
|  | SkMatrix44& matrix = transform->matrix(); | 
|  | matrix.set(0, 0, 10.f); | 
|  | matrix.set(1, 0, 11.f); | 
|  | matrix.set(2, 0, 12.f); | 
|  | matrix.set(3, 0, 13.f); | 
|  | matrix.set(0, 1, 14.f); | 
|  | matrix.set(1, 1, 15.f); | 
|  | matrix.set(2, 1, 16.f); | 
|  | matrix.set(3, 1, 17.f); | 
|  | matrix.set(0, 2, 18.f); | 
|  | matrix.set(1, 2, 19.f); | 
|  | matrix.set(2, 2, 20.f); | 
|  | matrix.set(3, 2, 21.f); | 
|  | matrix.set(0, 3, 22.f); | 
|  | matrix.set(1, 3, 23.f); | 
|  | matrix.set(2, 3, 24.f); | 
|  | matrix.set(3, 3, 25.f); | 
|  |  | 
|  | // Sanity check | 
|  | EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, (*transform)); | 
|  | EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, (*transform)); | 
|  | EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, (*transform)); | 
|  | EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, (*transform)); | 
|  | } | 
|  |  | 
|  | void InitializeTestMatrix2(Transform* transform) { | 
|  | SkMatrix44& matrix = transform->matrix(); | 
|  | matrix.set(0, 0, 30.f); | 
|  | matrix.set(1, 0, 31.f); | 
|  | matrix.set(2, 0, 32.f); | 
|  | matrix.set(3, 0, 33.f); | 
|  | matrix.set(0, 1, 34.f); | 
|  | matrix.set(1, 1, 35.f); | 
|  | matrix.set(2, 1, 36.f); | 
|  | matrix.set(3, 1, 37.f); | 
|  | matrix.set(0, 2, 38.f); | 
|  | matrix.set(1, 2, 39.f); | 
|  | matrix.set(2, 2, 40.f); | 
|  | matrix.set(3, 2, 41.f); | 
|  | matrix.set(0, 3, 42.f); | 
|  | matrix.set(1, 3, 43.f); | 
|  | matrix.set(2, 3, 44.f); | 
|  | matrix.set(3, 3, 45.f); | 
|  |  | 
|  | // Sanity check | 
|  | EXPECT_ROW1_EQ(30.0f, 34.0f, 38.0f, 42.0f, (*transform)); | 
|  | EXPECT_ROW2_EQ(31.0f, 35.0f, 39.0f, 43.0f, (*transform)); | 
|  | EXPECT_ROW3_EQ(32.0f, 36.0f, 40.0f, 44.0f, (*transform)); | 
|  | EXPECT_ROW4_EQ(33.0f, 37.0f, 41.0f, 45.0f, (*transform)); | 
|  | } | 
|  |  | 
|  | const SkMScalar kApproxZero = | 
|  | SkFloatToMScalar(std::numeric_limits<float>::epsilon()); | 
|  | const SkMScalar kApproxOne = 1 - kApproxZero; | 
|  |  | 
|  | void InitializeApproxIdentityMatrix(Transform* transform) { | 
|  | SkMatrix44& matrix = transform->matrix(); | 
|  | matrix.set(0, 0, kApproxOne); | 
|  | matrix.set(0, 1, kApproxZero); | 
|  | matrix.set(0, 2, kApproxZero); | 
|  | matrix.set(0, 3, kApproxZero); | 
|  |  | 
|  | matrix.set(1, 0, kApproxZero); | 
|  | matrix.set(1, 1, kApproxOne); | 
|  | matrix.set(1, 2, kApproxZero); | 
|  | matrix.set(1, 3, kApproxZero); | 
|  |  | 
|  | matrix.set(2, 0, kApproxZero); | 
|  | matrix.set(2, 1, kApproxZero); | 
|  | matrix.set(2, 2, kApproxOne); | 
|  | matrix.set(2, 3, kApproxZero); | 
|  |  | 
|  | matrix.set(3, 0, kApproxZero); | 
|  | matrix.set(3, 1, kApproxZero); | 
|  | matrix.set(3, 2, kApproxZero); | 
|  | matrix.set(3, 3, kApproxOne); | 
|  | } | 
|  |  | 
|  | #ifdef SK_MSCALAR_IS_DOUBLE | 
|  | #define ERROR_THRESHOLD 1e-14 | 
|  | #else | 
|  | #define ERROR_THRESHOLD 1e-7 | 
|  | #endif | 
|  | #define LOOSE_ERROR_THRESHOLD 1e-7 | 
|  |  | 
|  | TEST(XFormTest, Equality) { | 
|  | Transform lhs, rhs, interpolated; | 
|  | rhs.matrix().set3x3(1, 2, 3, | 
|  | 4, 5, 6, | 
|  | 7, 8, 9); | 
|  | interpolated = lhs; | 
|  | for (int i = 0; i <= 100; ++i) { | 
|  | for (int row = 0; row < 4; ++row) { | 
|  | for (int col = 0; col < 4; ++col) { | 
|  | float a = lhs.matrix().get(row, col); | 
|  | float b = rhs.matrix().get(row, col); | 
|  | float t = i / 100.0f; | 
|  | interpolated.matrix().set(row, col, a + (b - a) * t); | 
|  | } | 
|  | } | 
|  | if (i == 100) { | 
|  | EXPECT_TRUE(rhs == interpolated); | 
|  | } else { | 
|  | EXPECT_TRUE(rhs != interpolated); | 
|  | } | 
|  | } | 
|  | lhs = Transform(); | 
|  | rhs = Transform(); | 
|  | for (int i = 1; i < 100; ++i) { | 
|  | lhs.MakeIdentity(); | 
|  | rhs.MakeIdentity(); | 
|  | lhs.Translate(i, i); | 
|  | rhs.Translate(-i, -i); | 
|  | EXPECT_TRUE(lhs != rhs); | 
|  | rhs.Translate(2*i, 2*i); | 
|  | EXPECT_TRUE(lhs == rhs); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, ConcatTranslate) { | 
|  | static const struct TestCase { | 
|  | int x1; | 
|  | int y1; | 
|  | float tx; | 
|  | float ty; | 
|  | int x2; | 
|  | int y2; | 
|  | } test_cases[] = { | 
|  | { 0, 0, 10.0f, 20.0f, 10, 20 }, | 
|  | { 0, 0, -10.0f, -20.0f, 0, 0 }, | 
|  | { 0, 0, -10.0f, -20.0f, -10, -20 }, | 
|  | { 0, 0, | 
|  | std::numeric_limits<float>::quiet_NaN(), | 
|  | std::numeric_limits<float>::quiet_NaN(), | 
|  | 10, 20 }, | 
|  | }; | 
|  |  | 
|  | Transform xform; | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | Transform translation; | 
|  | translation.Translate(value.tx, value.ty); | 
|  | xform = translation * xform; | 
|  | Point3F p1(value.x1, value.y1, 0); | 
|  | Point3F p2(value.x2, value.y2, 0); | 
|  | xform.TransformPoint(&p1); | 
|  | if (value.tx == value.tx && | 
|  | value.ty == value.ty) { | 
|  | EXPECT_TRUE(PointsAreNearlyEqual(p1, p2)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, ConcatScale) { | 
|  | static const struct TestCase { | 
|  | int before; | 
|  | float scale; | 
|  | int after; | 
|  | } test_cases[] = { | 
|  | { 1, 10.0f, 10 }, | 
|  | { 1, .1f, 1 }, | 
|  | { 1, 100.0f, 100 }, | 
|  | { 1, -1.0f, -100 }, | 
|  | { 1, std::numeric_limits<float>::quiet_NaN(), 1 } | 
|  | }; | 
|  |  | 
|  | Transform xform; | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | Transform scale; | 
|  | scale.Scale(value.scale, value.scale); | 
|  | xform = scale * xform; | 
|  | Point3F p1(value.before, value.before, 0); | 
|  | Point3F p2(value.after, value.after, 0); | 
|  | xform.TransformPoint(&p1); | 
|  | if (value.scale == value.scale) { | 
|  | EXPECT_TRUE(PointsAreNearlyEqual(p1, p2)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, ConcatRotate) { | 
|  | static const struct TestCase { | 
|  | int x1; | 
|  | int y1; | 
|  | float degrees; | 
|  | int x2; | 
|  | int y2; | 
|  | } test_cases[] = { | 
|  | { 1, 0, 90.0f, 0, 1 }, | 
|  | { 1, 0, -90.0f, 1, 0 }, | 
|  | { 1, 0, 90.0f, 0, 1 }, | 
|  | { 1, 0, 360.0f, 0, 1 }, | 
|  | { 1, 0, 0.0f, 0, 1 }, | 
|  | { 1, 0, std::numeric_limits<float>::quiet_NaN(), 1, 0 } | 
|  | }; | 
|  |  | 
|  | Transform xform; | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | Transform rotation; | 
|  | rotation.Rotate(value.degrees); | 
|  | xform = rotation * xform; | 
|  | Point3F p1(value.x1, value.y1, 0); | 
|  | Point3F p2(value.x2, value.y2, 0); | 
|  | xform.TransformPoint(&p1); | 
|  | if (value.degrees == value.degrees) { | 
|  | EXPECT_TRUE(PointsAreNearlyEqual(p1, p2)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, SetTranslate) { | 
|  | static const struct TestCase { | 
|  | int x1; int y1; | 
|  | float tx; float ty; | 
|  | int x2; int y2; | 
|  | } test_cases[] = { | 
|  | { 0, 0, 10.0f, 20.0f, 10, 20 }, | 
|  | { 10, 20, 10.0f, 20.0f, 20, 40 }, | 
|  | { 10, 20, 0.0f, 0.0f, 10, 20 }, | 
|  | { 0, 0, | 
|  | std::numeric_limits<float>::quiet_NaN(), | 
|  | std::numeric_limits<float>::quiet_NaN(), | 
|  | 0, 0 } | 
|  | }; | 
|  |  | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | for (int k = 0; k < 3; ++k) { | 
|  | Point3F p0, p1, p2; | 
|  | Transform xform; | 
|  | switch (k) { | 
|  | case 0: | 
|  | p1.SetPoint(value.x1, 0, 0); | 
|  | p2.SetPoint(value.x2, 0, 0); | 
|  | xform.Translate(value.tx, 0.0); | 
|  | break; | 
|  | case 1: | 
|  | p1.SetPoint(0, value.y1, 0); | 
|  | p2.SetPoint(0, value.y2, 0); | 
|  | xform.Translate(0.0, value.ty); | 
|  | break; | 
|  | case 2: | 
|  | p1.SetPoint(value.x1, value.y1, 0); | 
|  | p2.SetPoint(value.x2, value.y2, 0); | 
|  | xform.Translate(value.tx, value.ty); | 
|  | break; | 
|  | } | 
|  | p0 = p1; | 
|  | xform.TransformPoint(&p1); | 
|  | if (value.tx == value.tx && | 
|  | value.ty == value.ty) { | 
|  | EXPECT_TRUE(PointsAreNearlyEqual(p1, p2)); | 
|  | xform.TransformPointReverse(&p1); | 
|  | EXPECT_TRUE(PointsAreNearlyEqual(p1, p0)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, SetScale) { | 
|  | static const struct TestCase { | 
|  | int before; | 
|  | float s; | 
|  | int after; | 
|  | } test_cases[] = { | 
|  | { 1, 10.0f, 10 }, | 
|  | { 1, 1.0f, 1 }, | 
|  | { 1, 0.0f, 0 }, | 
|  | { 0, 10.0f, 0 }, | 
|  | { 1, std::numeric_limits<float>::quiet_NaN(), 0 }, | 
|  | }; | 
|  |  | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | for (int k = 0; k < 3; ++k) { | 
|  | Point3F p0, p1, p2; | 
|  | Transform xform; | 
|  | switch (k) { | 
|  | case 0: | 
|  | p1.SetPoint(value.before, 0, 0); | 
|  | p2.SetPoint(value.after, 0, 0); | 
|  | xform.Scale(value.s, 1.0); | 
|  | break; | 
|  | case 1: | 
|  | p1.SetPoint(0, value.before, 0); | 
|  | p2.SetPoint(0, value.after, 0); | 
|  | xform.Scale(1.0, value.s); | 
|  | break; | 
|  | case 2: | 
|  | p1.SetPoint(value.before, value.before, 0); | 
|  | p2.SetPoint(value.after, value.after, 0); | 
|  | xform.Scale(value.s, value.s); | 
|  | break; | 
|  | } | 
|  | p0 = p1; | 
|  | xform.TransformPoint(&p1); | 
|  | if (value.s == value.s) { | 
|  | EXPECT_TRUE(PointsAreNearlyEqual(p1, p2)); | 
|  | if (value.s != 0.0f) { | 
|  | xform.TransformPointReverse(&p1); | 
|  | EXPECT_TRUE(PointsAreNearlyEqual(p1, p0)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, SetRotate) { | 
|  | static const struct SetRotateCase { | 
|  | int x; | 
|  | int y; | 
|  | float degree; | 
|  | int xprime; | 
|  | int yprime; | 
|  | } set_rotate_cases[] = { | 
|  | { 100, 0, 90.0f, 0, 100 }, | 
|  | { 0, 0, 90.0f, 0, 0 }, | 
|  | { 0, 100, 90.0f, -100, 0 }, | 
|  | { 0, 1, -90.0f, 1, 0 }, | 
|  | { 100, 0, 0.0f, 100, 0 }, | 
|  | { 0, 0, 0.0f, 0, 0 }, | 
|  | { 0, 0, std::numeric_limits<float>::quiet_NaN(), 0, 0 }, | 
|  | { 100, 0, 360.0f, 100, 0 } | 
|  | }; | 
|  |  | 
|  | for (size_t i = 0; i < arraysize(set_rotate_cases); ++i) { | 
|  | const SetRotateCase& value = set_rotate_cases[i]; | 
|  | Point3F p0; | 
|  | Point3F p1(value.x, value.y, 0); | 
|  | Point3F p2(value.xprime, value.yprime, 0); | 
|  | p0 = p1; | 
|  | Transform xform; | 
|  | xform.Rotate(value.degree); | 
|  | // just want to make sure that we don't crash in the case of NaN. | 
|  | if (value.degree == value.degree) { | 
|  | xform.TransformPoint(&p1); | 
|  | EXPECT_TRUE(PointsAreNearlyEqual(p1, p2)); | 
|  | xform.TransformPointReverse(&p1); | 
|  | EXPECT_TRUE(PointsAreNearlyEqual(p1, p0)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // 2D tests | 
|  | TEST(XFormTest, ConcatTranslate2D) { | 
|  | static const struct TestCase { | 
|  | int x1; | 
|  | int y1; | 
|  | float tx; | 
|  | float ty; | 
|  | int x2; | 
|  | int y2; | 
|  | } test_cases[] = { | 
|  | { 0, 0, 10.0f, 20.0f, 10, 20}, | 
|  | { 0, 0, -10.0f, -20.0f, 0, 0}, | 
|  | { 0, 0, -10.0f, -20.0f, -10, -20}, | 
|  | { 0, 0, | 
|  | std::numeric_limits<float>::quiet_NaN(), | 
|  | std::numeric_limits<float>::quiet_NaN(), | 
|  | 10, 20}, | 
|  | }; | 
|  |  | 
|  | Transform xform; | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | Transform translation; | 
|  | translation.Translate(value.tx, value.ty); | 
|  | xform = translation * xform; | 
|  | Point p1(value.x1, value.y1); | 
|  | Point p2(value.x2, value.y2); | 
|  | xform.TransformPoint(&p1); | 
|  | if (value.tx == value.tx && | 
|  | value.ty == value.ty) { | 
|  | EXPECT_EQ(p1.x(), p2.x()); | 
|  | EXPECT_EQ(p1.y(), p2.y()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, ConcatScale2D) { | 
|  | static const struct TestCase { | 
|  | int before; | 
|  | float scale; | 
|  | int after; | 
|  | } test_cases[] = { | 
|  | { 1, 10.0f, 10}, | 
|  | { 1, .1f, 1}, | 
|  | { 1, 100.0f, 100}, | 
|  | { 1, -1.0f, -100}, | 
|  | { 1, std::numeric_limits<float>::quiet_NaN(), 1} | 
|  | }; | 
|  |  | 
|  | Transform xform; | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | Transform scale; | 
|  | scale.Scale(value.scale, value.scale); | 
|  | xform = scale * xform; | 
|  | Point p1(value.before, value.before); | 
|  | Point p2(value.after, value.after); | 
|  | xform.TransformPoint(&p1); | 
|  | if (value.scale == value.scale) { | 
|  | EXPECT_EQ(p1.x(), p2.x()); | 
|  | EXPECT_EQ(p1.y(), p2.y()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, ConcatRotate2D) { | 
|  | static const struct TestCase { | 
|  | int x1; | 
|  | int y1; | 
|  | float degrees; | 
|  | int x2; | 
|  | int y2; | 
|  | } test_cases[] = { | 
|  | { 1, 0, 90.0f, 0, 1}, | 
|  | { 1, 0, -90.0f, 1, 0}, | 
|  | { 1, 0, 90.0f, 0, 1}, | 
|  | { 1, 0, 360.0f, 0, 1}, | 
|  | { 1, 0, 0.0f, 0, 1}, | 
|  | { 1, 0, std::numeric_limits<float>::quiet_NaN(), 1, 0} | 
|  | }; | 
|  |  | 
|  | Transform xform; | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | Transform rotation; | 
|  | rotation.Rotate(value.degrees); | 
|  | xform = rotation * xform; | 
|  | Point p1(value.x1, value.y1); | 
|  | Point p2(value.x2, value.y2); | 
|  | xform.TransformPoint(&p1); | 
|  | if (value.degrees == value.degrees) { | 
|  | EXPECT_EQ(p1.x(), p2.x()); | 
|  | EXPECT_EQ(p1.y(), p2.y()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, SetTranslate2D) { | 
|  | static const struct TestCase { | 
|  | int x1; int y1; | 
|  | float tx; float ty; | 
|  | int x2; int y2; | 
|  | } test_cases[] = { | 
|  | { 0, 0, 10.0f, 20.0f, 10, 20}, | 
|  | { 10, 20, 10.0f, 20.0f, 20, 40}, | 
|  | { 10, 20, 0.0f, 0.0f, 10, 20}, | 
|  | { 0, 0, | 
|  | std::numeric_limits<float>::quiet_NaN(), | 
|  | std::numeric_limits<float>::quiet_NaN(), | 
|  | 0, 0} | 
|  | }; | 
|  |  | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | for (int j = -1; j < 2; ++j) { | 
|  | for (int k = 0; k < 3; ++k) { | 
|  | float epsilon = 0.0001f; | 
|  | Point p0, p1, p2; | 
|  | Transform xform; | 
|  | switch (k) { | 
|  | case 0: | 
|  | p1.SetPoint(value.x1, 0); | 
|  | p2.SetPoint(value.x2, 0); | 
|  | xform.Translate(value.tx + j * epsilon, 0.0); | 
|  | break; | 
|  | case 1: | 
|  | p1.SetPoint(0, value.y1); | 
|  | p2.SetPoint(0, value.y2); | 
|  | xform.Translate(0.0, value.ty + j * epsilon); | 
|  | break; | 
|  | case 2: | 
|  | p1.SetPoint(value.x1, value.y1); | 
|  | p2.SetPoint(value.x2, value.y2); | 
|  | xform.Translate(value.tx + j * epsilon, | 
|  | value.ty + j * epsilon); | 
|  | break; | 
|  | } | 
|  | p0 = p1; | 
|  | xform.TransformPoint(&p1); | 
|  | if (value.tx == value.tx && | 
|  | value.ty == value.ty) { | 
|  | EXPECT_EQ(p1.x(), p2.x()); | 
|  | EXPECT_EQ(p1.y(), p2.y()); | 
|  | xform.TransformPointReverse(&p1); | 
|  | EXPECT_EQ(p1.x(), p0.x()); | 
|  | EXPECT_EQ(p1.y(), p0.y()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, SetScale2D) { | 
|  | static const struct TestCase { | 
|  | int before; | 
|  | float s; | 
|  | int after; | 
|  | } test_cases[] = { | 
|  | { 1, 10.0f, 10}, | 
|  | { 1, 1.0f, 1}, | 
|  | { 1, 0.0f, 0}, | 
|  | { 0, 10.0f, 0}, | 
|  | { 1, std::numeric_limits<float>::quiet_NaN(), 0}, | 
|  | }; | 
|  |  | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | for (int j = -1; j < 2; ++j) { | 
|  | for (int k = 0; k < 3; ++k) { | 
|  | float epsilon = 0.0001f; | 
|  | Point p0, p1, p2; | 
|  | Transform xform; | 
|  | switch (k) { | 
|  | case 0: | 
|  | p1.SetPoint(value.before, 0); | 
|  | p2.SetPoint(value.after, 0); | 
|  | xform.Scale(value.s + j * epsilon, 1.0); | 
|  | break; | 
|  | case 1: | 
|  | p1.SetPoint(0, value.before); | 
|  | p2.SetPoint(0, value.after); | 
|  | xform.Scale(1.0, value.s + j * epsilon); | 
|  | break; | 
|  | case 2: | 
|  | p1.SetPoint(value.before, | 
|  | value.before); | 
|  | p2.SetPoint(value.after, | 
|  | value.after); | 
|  | xform.Scale(value.s + j * epsilon, | 
|  | value.s + j * epsilon); | 
|  | break; | 
|  | } | 
|  | p0 = p1; | 
|  | xform.TransformPoint(&p1); | 
|  | if (value.s == value.s) { | 
|  | EXPECT_EQ(p1.x(), p2.x()); | 
|  | EXPECT_EQ(p1.y(), p2.y()); | 
|  | if (value.s != 0.0f) { | 
|  | xform.TransformPointReverse(&p1); | 
|  | EXPECT_EQ(p1.x(), p0.x()); | 
|  | EXPECT_EQ(p1.y(), p0.y()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, SetRotate2D) { | 
|  | static const struct SetRotateCase { | 
|  | int x; | 
|  | int y; | 
|  | float degree; | 
|  | int xprime; | 
|  | int yprime; | 
|  | } set_rotate_cases[] = { | 
|  | { 100, 0, 90.0f, 0, 100}, | 
|  | { 0, 0, 90.0f, 0, 0}, | 
|  | { 0, 100, 90.0f, -100, 0}, | 
|  | { 0, 1, -90.0f, 1, 0}, | 
|  | { 100, 0, 0.0f, 100, 0}, | 
|  | { 0, 0, 0.0f, 0, 0}, | 
|  | { 0, 0, std::numeric_limits<float>::quiet_NaN(), 0, 0}, | 
|  | { 100, 0, 360.0f, 100, 0} | 
|  | }; | 
|  |  | 
|  | for (size_t i = 0; i < arraysize(set_rotate_cases); ++i) { | 
|  | const SetRotateCase& value = set_rotate_cases[i]; | 
|  | for (int j = 1; j >= -1; --j) { | 
|  | float epsilon = 0.1f; | 
|  | Point pt(value.x, value.y); | 
|  | Transform xform; | 
|  | // should be invariant to small floating point errors. | 
|  | xform.Rotate(value.degree + j * epsilon); | 
|  | // just want to make sure that we don't crash in the case of NaN. | 
|  | if (value.degree == value.degree) { | 
|  | xform.TransformPoint(&pt); | 
|  | EXPECT_EQ(value.xprime, pt.x()); | 
|  | EXPECT_EQ(value.yprime, pt.y()); | 
|  | xform.TransformPointReverse(&pt); | 
|  | EXPECT_EQ(pt.x(), value.x); | 
|  | EXPECT_EQ(pt.y(), value.y); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, TransformPointWithExtremePerspective) { | 
|  | Point3F point(1.f, 1.f, 1.f); | 
|  | Transform perspective; | 
|  | perspective.ApplyPerspectiveDepth(1.f); | 
|  | Point3F transformed = point; | 
|  | perspective.TransformPoint(&transformed); | 
|  | EXPECT_EQ(point.ToString(), transformed.ToString()); | 
|  |  | 
|  | transformed = point; | 
|  | perspective.MakeIdentity(); | 
|  | perspective.ApplyPerspectiveDepth(1.1f); | 
|  | perspective.TransformPoint(&transformed); | 
|  | EXPECT_FLOAT_EQ(11.f, transformed.x()); | 
|  | EXPECT_FLOAT_EQ(11.f, transformed.y()); | 
|  | EXPECT_FLOAT_EQ(11.f, transformed.z()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, BlendTranslate) { | 
|  | Transform from; | 
|  | for (int i = -5; i < 15; ++i) { | 
|  | Transform to; | 
|  | to.Translate3d(1, 1, 1); | 
|  | double t = i / 9.0; | 
|  | EXPECT_TRUE(to.Blend(from, t)); | 
|  | EXPECT_FLOAT_EQ(t, to.matrix().get(0, 3)); | 
|  | EXPECT_FLOAT_EQ(t, to.matrix().get(1, 3)); | 
|  | EXPECT_FLOAT_EQ(t, to.matrix().get(2, 3)); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, BlendRotate) { | 
|  | Vector3dF axes[] = { | 
|  | Vector3dF(1, 0, 0), | 
|  | Vector3dF(0, 1, 0), | 
|  | Vector3dF(0, 0, 1), | 
|  | Vector3dF(1, 1, 1) | 
|  | }; | 
|  | Transform from; | 
|  | for (size_t index = 0; index < arraysize(axes); ++index) { | 
|  | for (int i = -5; i < 15; ++i) { | 
|  | Transform to; | 
|  | to.RotateAbout(axes[index], 90); | 
|  | double t = i / 9.0; | 
|  | EXPECT_TRUE(to.Blend(from, t)); | 
|  |  | 
|  | Transform expected; | 
|  | expected.RotateAbout(axes[index], 90 * t); | 
|  |  | 
|  | EXPECT_TRUE(MatricesAreNearlyEqual(expected, to)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | #if defined(_WIN64) | 
|  | // http://crbug.com/406574 | 
|  | #define MAYBE_BlendRotateFollowsShortestPath DISABLED_BlendRotateFollowsShortestPath | 
|  | #else | 
|  | #define MAYBE_BlendRotateFollowsShortestPath BlendRotateFollowsShortestPath | 
|  | #endif | 
|  | TEST(XFormTest, MAYBE_BlendRotateFollowsShortestPath) { | 
|  | // Verify that we interpolate along the shortest path regardless of whether | 
|  | // this path crosses the 180-degree point. | 
|  | Vector3dF axes[] = { | 
|  | Vector3dF(1, 0, 0), | 
|  | Vector3dF(0, 1, 0), | 
|  | Vector3dF(0, 0, 1), | 
|  | Vector3dF(1, 1, 1) | 
|  | }; | 
|  | for (size_t index = 0; index < arraysize(axes); ++index) { | 
|  | for (int i = -5; i < 15; ++i) { | 
|  | Transform from1; | 
|  | from1.RotateAbout(axes[index], 130.0); | 
|  | Transform to1; | 
|  | to1.RotateAbout(axes[index], 175.0); | 
|  |  | 
|  | Transform from2; | 
|  | from2.RotateAbout(axes[index], 140.0); | 
|  | Transform to2; | 
|  | to2.RotateAbout(axes[index], 185.0); | 
|  |  | 
|  | double t = i / 9.0; | 
|  | EXPECT_TRUE(to1.Blend(from1, t)); | 
|  | EXPECT_TRUE(to2.Blend(from2, t)); | 
|  |  | 
|  | Transform expected1; | 
|  | expected1.RotateAbout(axes[index], 130.0 + 45.0 * t); | 
|  |  | 
|  | Transform expected2; | 
|  | expected2.RotateAbout(axes[index], 140.0 + 45.0 * t); | 
|  |  | 
|  | EXPECT_TRUE(MatricesAreNearlyEqual(expected1, to1)); | 
|  | EXPECT_TRUE(MatricesAreNearlyEqual(expected2, to2)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, CanBlend180DegreeRotation) { | 
|  | Vector3dF axes[] = { | 
|  | Vector3dF(1, 0, 0), | 
|  | Vector3dF(0, 1, 0), | 
|  | Vector3dF(0, 0, 1), | 
|  | Vector3dF(1, 1, 1) | 
|  | }; | 
|  | Transform from; | 
|  | for (size_t index = 0; index < arraysize(axes); ++index) { | 
|  | for (int i = -5; i < 15; ++i) { | 
|  | Transform to; | 
|  | to.RotateAbout(axes[index], 180.0); | 
|  | double t = i / 9.0; | 
|  | EXPECT_TRUE(to.Blend(from, t)); | 
|  |  | 
|  | // A 180 degree rotation is exactly opposite on the sphere, therefore | 
|  | // either great circle arc to it is equivalent (and numerical precision | 
|  | // will determine which is closer).  Test both directions. | 
|  | Transform expected1; | 
|  | expected1.RotateAbout(axes[index], 180.0 * t); | 
|  | Transform expected2; | 
|  | expected2.RotateAbout(axes[index], -180.0 * t); | 
|  |  | 
|  | EXPECT_TRUE(MatricesAreNearlyEqual(expected1, to) || | 
|  | MatricesAreNearlyEqual(expected2, to)) | 
|  | << "axis: " << index << ", i: " << i; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | #if defined(_WIN64) | 
|  | // http://crbug.com/406574 | 
|  | #define MAYBE_BlendScale DISABLED_BlendScale | 
|  | #else | 
|  | #define MAYBE_BlendScale BlendScale | 
|  | #endif | 
|  | TEST(XFormTest, MAYBE_BlendScale) { | 
|  | Transform from; | 
|  | for (int i = -5; i < 15; ++i) { | 
|  | Transform to; | 
|  | to.Scale3d(5, 4, 3); | 
|  | double t = i / 9.0; | 
|  | EXPECT_TRUE(to.Blend(from, t)); | 
|  | EXPECT_FLOAT_EQ(t * 4 + 1, to.matrix().get(0, 0)) << "i: " << i; | 
|  | EXPECT_FLOAT_EQ(t * 3 + 1, to.matrix().get(1, 1)) << "i: " << i; | 
|  | EXPECT_FLOAT_EQ(t * 2 + 1, to.matrix().get(2, 2)) << "i: " << i; | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, BlendSkew) { | 
|  | Transform from; | 
|  | for (int i = 0; i < 2; ++i) { | 
|  | Transform to; | 
|  | to.SkewX(10); | 
|  | to.SkewY(5); | 
|  | double t = i; | 
|  | Transform expected; | 
|  | expected.SkewX(t * 10); | 
|  | expected.SkewY(t * 5); | 
|  | EXPECT_TRUE(to.Blend(from, t)); | 
|  | EXPECT_TRUE(MatricesAreNearlyEqual(expected, to)); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, ExtrapolateSkew) { | 
|  | Transform from; | 
|  | for (int i = -1; i < 2; ++i) { | 
|  | Transform to; | 
|  | to.SkewX(20); | 
|  | double t = i; | 
|  | Transform expected; | 
|  | expected.SkewX(t * 20); | 
|  | EXPECT_TRUE(to.Blend(from, t)); | 
|  | EXPECT_TRUE(MatricesAreNearlyEqual(expected, to)); | 
|  | } | 
|  | } | 
|  |  | 
|  | #if defined(_WIN64) | 
|  | // http://crbug.com/406574 | 
|  | #define MAYBE_BlendPerspective DISABLED_BlendPerspective | 
|  | #else | 
|  | #define MAYBE_BlendPerspective BlendPerspective | 
|  | #endif | 
|  | TEST(XFormTest, MAYBE_BlendPerspective) { | 
|  | Transform from; | 
|  | from.ApplyPerspectiveDepth(200); | 
|  | for (int i = -1; i < 3; ++i) { | 
|  | Transform to; | 
|  | to.ApplyPerspectiveDepth(800); | 
|  | double t = i; | 
|  | double depth = 1.0 / ((1.0 / 200) * (1.0 - t) + (1.0 / 800) * t); | 
|  | Transform expected; | 
|  | expected.ApplyPerspectiveDepth(depth); | 
|  | EXPECT_TRUE(to.Blend(from, t)); | 
|  | EXPECT_TRUE(MatricesAreNearlyEqual(expected, to)); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, BlendIdentity) { | 
|  | Transform from; | 
|  | Transform to; | 
|  | EXPECT_TRUE(to.Blend(from, 0.5)); | 
|  | EXPECT_EQ(to, from); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, CannotBlendSingularMatrix) { | 
|  | Transform from; | 
|  | Transform to; | 
|  | to.matrix().set(1, 1, SkDoubleToMScalar(0)); | 
|  | EXPECT_FALSE(to.Blend(from, 0.5)); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, VerifyBlendForTranslation) { | 
|  | Transform from; | 
|  | from.Translate3d(100.0, 200.0, 100.0); | 
|  |  | 
|  | Transform to; | 
|  |  | 
|  | to.Translate3d(200.0, 100.0, 300.0); | 
|  | to.Blend(from, 0.0); | 
|  | EXPECT_EQ(from, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.Translate3d(200.0, 100.0, 300.0); | 
|  | to.Blend(from, 0.25); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 125.0f, to); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 175.0f, to); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 150.0f, to); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f,  1.0f,  to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.Translate3d(200.0, 100.0, 300.0); | 
|  | to.Blend(from, 0.5); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 150.0f, to); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 150.0f, to); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 200.0f, to); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f,  1.0f,  to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.Translate3d(200.0, 100.0, 300.0); | 
|  | to.Blend(from, 1.0); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 200.0f, to); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 100.0f, to); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 300.0f, to); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f,  1.0f,  to); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, VerifyBlendForScale) { | 
|  | Transform from; | 
|  | from.Scale3d(100.0, 200.0, 100.0); | 
|  |  | 
|  | Transform to; | 
|  |  | 
|  | to.Scale3d(200.0, 100.0, 300.0); | 
|  | to.Blend(from, 0.0); | 
|  | EXPECT_EQ(from, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.Scale3d(200.0, 100.0, 300.0); | 
|  | to.Blend(from, 0.25); | 
|  | EXPECT_ROW1_EQ(125.0f, 0.0f,  0.0f,  0.0f, to); | 
|  | EXPECT_ROW2_EQ(0.0f,  175.0f, 0.0f,  0.0f, to); | 
|  | EXPECT_ROW3_EQ(0.0f,   0.0f, 150.0f, 0.0f, to); | 
|  | EXPECT_ROW4_EQ(0.0f,   0.0f,  0.0f,  1.0f, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.Scale3d(200.0, 100.0, 300.0); | 
|  | to.Blend(from, 0.5); | 
|  | EXPECT_ROW1_EQ(150.0f, 0.0f,  0.0f,  0.0f, to); | 
|  | EXPECT_ROW2_EQ(0.0f,  150.0f, 0.0f,  0.0f, to); | 
|  | EXPECT_ROW3_EQ(0.0f,   0.0f, 200.0f, 0.0f, to); | 
|  | EXPECT_ROW4_EQ(0.0f,   0.0f,  0.0f,  1.0f, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.Scale3d(200.0, 100.0, 300.0); | 
|  | to.Blend(from, 1.0); | 
|  | EXPECT_ROW1_EQ(200.0f, 0.0f,  0.0f,  0.0f, to); | 
|  | EXPECT_ROW2_EQ(0.0f,  100.0f, 0.0f,  0.0f, to); | 
|  | EXPECT_ROW3_EQ(0.0f,   0.0f, 300.0f, 0.0f, to); | 
|  | EXPECT_ROW4_EQ(0.0f,   0.0f,  0.0f,  1.0f, to); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, VerifyBlendForSkewX) { | 
|  | Transform from; | 
|  | from.SkewX(0.0); | 
|  |  | 
|  | Transform to; | 
|  |  | 
|  | to.SkewX(45.0); | 
|  | to.Blend(from, 0.0); | 
|  | EXPECT_EQ(from, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.SkewX(45.0); | 
|  | to.Blend(from, 0.5); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.5f, 0.0f, 0.0f, to); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, to); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.SkewX(45.0); | 
|  | to.Blend(from, 0.25); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.25f, 0.0f, 0.0f, to); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f,  0.0f, 0.0f, to); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f,  1.0f, 0.0f, to); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f,  0.0f, 1.0f, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.SkewX(45.0); | 
|  | to.Blend(from, 1.0); | 
|  | EXPECT_ROW1_EQ(1.0f, 1.0f, 0.0f, 0.0f, to); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, to); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, VerifyBlendForSkewY) { | 
|  | // 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. | 
|  | // | 
|  | // Unfortunately, this case suffers from uncomfortably large precision | 
|  | // error. | 
|  |  | 
|  | Transform from; | 
|  | from.SkewY(0.0); | 
|  |  | 
|  | Transform to; | 
|  |  | 
|  | to.SkewY(45.0); | 
|  | to.Blend(from, 0.0); | 
|  | EXPECT_EQ(from, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.SkewY(45.0); | 
|  | to.Blend(from, 0.25); | 
|  | EXPECT_ROW1_NEAR(1.0823489449280947471976333, | 
|  | 0.0464370719145053845178239, | 
|  | 0.0, | 
|  | 0.0, | 
|  | to, | 
|  | LOOSE_ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(0.2152925909665224513123150, | 
|  | 0.9541702441750861130032035, | 
|  | 0.0, | 
|  | 0.0, | 
|  | to, | 
|  | LOOSE_ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.SkewY(45.0); | 
|  | to.Blend(from, 0.5); | 
|  | EXPECT_ROW1_NEAR(1.1152212925809066312865525, | 
|  | 0.0676495144007326631996335, | 
|  | 0.0, | 
|  | 0.0, | 
|  | to, | 
|  | LOOSE_ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(0.4619397844342648662419037, | 
|  | 0.9519009045724774464858342, | 
|  | 0.0, | 
|  | 0.0, | 
|  | to, | 
|  | LOOSE_ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.SkewY(45.0); | 
|  | to.Blend(from, 1.0); | 
|  | EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, LOOSE_ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(1.0, 1.0, 0.0, 0.0, to, LOOSE_ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  | } | 
|  |  | 
|  | #if defined(_WIN64) | 
|  | // http://crbug.com/406574 | 
|  | #define MAYBE_VerifyBlendForRotationAboutX DISABLED_VerifyBlendForRotationAboutX | 
|  | #else | 
|  | #define MAYBE_VerifyBlendForRotationAboutX VerifyBlendForRotationAboutX | 
|  | #endif | 
|  | TEST(XFormTest, MAYBE_VerifyBlendForRotationAboutX) { | 
|  | // 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. | 
|  |  | 
|  | Transform from; | 
|  | from.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 0.0); | 
|  |  | 
|  | Transform to; | 
|  |  | 
|  | to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0); | 
|  | to.Blend(from, 0.0); | 
|  | EXPECT_EQ(from, to); | 
|  |  | 
|  | double expectedRotationAngle = 22.5 * M_PI / 180.0; | 
|  | to = Transform(); | 
|  | to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0); | 
|  | to.Blend(from, 0.25); | 
|  | EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(0.0, | 
|  | std::cos(expectedRotationAngle), | 
|  | -std::sin(expectedRotationAngle), | 
|  | 0.0, | 
|  | to, | 
|  | ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(0.0, | 
|  | std::sin(expectedRotationAngle), | 
|  | std::cos(expectedRotationAngle), | 
|  | 0.0, | 
|  | to, | 
|  | ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  |  | 
|  | expectedRotationAngle = 45.0 * M_PI / 180.0; | 
|  | to = Transform(); | 
|  | to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0); | 
|  | to.Blend(from, 0.5); | 
|  | EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(0.0, | 
|  | std::cos(expectedRotationAngle), | 
|  | -std::sin(expectedRotationAngle), | 
|  | 0.0, | 
|  | to, | 
|  | ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(0.0, | 
|  | std::sin(expectedRotationAngle), | 
|  | std::cos(expectedRotationAngle), | 
|  | 0.0, | 
|  | to, | 
|  | ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0); | 
|  | to.Blend(from, 1.0); | 
|  | EXPECT_ROW1_NEAR(1.0, 0.0,  0.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(0.0, 0.0, -1.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(0.0, 1.0,  0.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  | } | 
|  |  | 
|  | #if defined(_WIN64) | 
|  | // http://crbug.com/406574 | 
|  | #define MAYBE_VerifyBlendForRotationAboutY DISABLED_VerifyBlendForRotationAboutY | 
|  | #else | 
|  | #define MAYBE_VerifyBlendForRotationAboutY VerifyBlendForRotationAboutY | 
|  | #endif | 
|  | TEST(XFormTest, MAYBE_VerifyBlendForRotationAboutY) { | 
|  | Transform from; | 
|  | from.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 0.0); | 
|  |  | 
|  | Transform to; | 
|  |  | 
|  | to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0); | 
|  | to.Blend(from, 0.0); | 
|  | EXPECT_EQ(from, to); | 
|  |  | 
|  | double expectedRotationAngle = 22.5 * M_PI / 180.0; | 
|  | to = Transform(); | 
|  | to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0); | 
|  | to.Blend(from, 0.25); | 
|  | EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle), | 
|  | 0.0, | 
|  | std::sin(expectedRotationAngle), | 
|  | 0.0, | 
|  | to, | 
|  | ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(0.0, 1.0, 0.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(-std::sin(expectedRotationAngle), | 
|  | 0.0, | 
|  | std::cos(expectedRotationAngle), | 
|  | 0.0, | 
|  | to, | 
|  | ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  |  | 
|  | expectedRotationAngle = 45.0 * M_PI / 180.0; | 
|  | to = Transform(); | 
|  | to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0); | 
|  | to.Blend(from, 0.5); | 
|  | EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle), | 
|  | 0.0, | 
|  | std::sin(expectedRotationAngle), | 
|  | 0.0, | 
|  | to, | 
|  | ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(0.0, 1.0, 0.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(-std::sin(expectedRotationAngle), | 
|  | 0.0, | 
|  | std::cos(expectedRotationAngle), | 
|  | 0.0, | 
|  | to, | 
|  | ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0); | 
|  | to.Blend(from, 1.0); | 
|  | EXPECT_ROW1_NEAR(0.0,  0.0, 1.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(0.0,  1.0, 0.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(-1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  | } | 
|  |  | 
|  | #if defined(_WIN64) | 
|  | // http://crbug.com/406574 | 
|  | #define MAYBE_VerifyBlendForRotationAboutZ DISABLED_VerifyBlendForRotationAboutZ | 
|  | #else | 
|  | #define MAYBE_VerifyBlendForRotationAboutZ VerifyBlendForRotationAboutZ | 
|  | #endif | 
|  | TEST(XFormTest, MAYBE_VerifyBlendForRotationAboutZ) { | 
|  | Transform from; | 
|  | from.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 0.0); | 
|  |  | 
|  | Transform to; | 
|  |  | 
|  | to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0); | 
|  | to.Blend(from, 0.0); | 
|  | EXPECT_EQ(from, to); | 
|  |  | 
|  | double expectedRotationAngle = 22.5 * M_PI / 180.0; | 
|  | to = Transform(); | 
|  | to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0); | 
|  | to.Blend(from, 0.25); | 
|  | EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle), | 
|  | -std::sin(expectedRotationAngle), | 
|  | 0.0, | 
|  | 0.0, | 
|  | to, | 
|  | ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(std::sin(expectedRotationAngle), | 
|  | std::cos(expectedRotationAngle), | 
|  | 0.0, | 
|  | 0.0, | 
|  | to, | 
|  | ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(0.0, 0.0, 1.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  |  | 
|  | expectedRotationAngle = 45.0 * M_PI / 180.0; | 
|  | to = Transform(); | 
|  | to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0); | 
|  | to.Blend(from, 0.5); | 
|  | EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle), | 
|  | -std::sin(expectedRotationAngle), | 
|  | 0.0, | 
|  | 0.0, | 
|  | to, | 
|  | ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(std::sin(expectedRotationAngle), | 
|  | std::cos(expectedRotationAngle), | 
|  | 0.0, | 
|  | 0.0, | 
|  | to, | 
|  | ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(0.0, 0.0, 1.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  |  | 
|  | to = Transform(); | 
|  | to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0); | 
|  | to.Blend(from, 1.0); | 
|  | EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(1.0,  0.0, 0.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(0.0,  0.0, 1.0, 0.0, to, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, VerifyBlendForCompositeTransform) { | 
|  | // 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. | 
|  |  | 
|  | Transform from; | 
|  | Transform to; | 
|  |  | 
|  | Transform expectedEndOfAnimation; | 
|  | expectedEndOfAnimation.ApplyPerspectiveDepth(1.0); | 
|  | expectedEndOfAnimation.Translate3d(10.0, 20.0, 30.0); | 
|  | expectedEndOfAnimation.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 25.0); | 
|  | expectedEndOfAnimation.SkewY(45.0); | 
|  | expectedEndOfAnimation.Scale3d(6.0, 7.0, 8.0); | 
|  |  | 
|  | to = expectedEndOfAnimation; | 
|  | to.Blend(from, 0.0); | 
|  | EXPECT_EQ(from, to); | 
|  |  | 
|  | to = expectedEndOfAnimation; | 
|  | // We short circuit if blend is >= 1, so to check the numerics, we will | 
|  | // check that we get close to what we expect when we're nearly done | 
|  | // interpolating. | 
|  | to.Blend(from, .99999f); | 
|  |  | 
|  | // Recomposing the matrix results in a normalized matrix, so to verify we | 
|  | // need to normalize the expectedEndOfAnimation before comparing elements. | 
|  | // Normalizing means dividing everything by expectedEndOfAnimation.m44(). | 
|  | Transform normalizedExpectedEndOfAnimation = expectedEndOfAnimation; | 
|  | Transform normalizationMatrix; | 
|  | normalizationMatrix.matrix().set( | 
|  | 0.0, | 
|  | 0.0, | 
|  | SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0))); | 
|  | normalizationMatrix.matrix().set( | 
|  | 1.0, | 
|  | 1.0, | 
|  | SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0))); | 
|  | normalizationMatrix.matrix().set( | 
|  | 2.0, | 
|  | 2.0, | 
|  | SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0))); | 
|  | normalizationMatrix.matrix().set( | 
|  | 3.0, | 
|  | 3.0, | 
|  | SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0))); | 
|  | normalizedExpectedEndOfAnimation.PreconcatTransform(normalizationMatrix); | 
|  |  | 
|  | EXPECT_TRUE(MatricesAreNearlyEqual(normalizedExpectedEndOfAnimation, to)); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, DecomposedTransformCtor) { | 
|  | DecomposedTransform decomp; | 
|  | for (int i = 0; i < 3; ++i) { | 
|  | EXPECT_EQ(0.0, decomp.translate[i]); | 
|  | EXPECT_EQ(1.0, decomp.scale[i]); | 
|  | EXPECT_EQ(0.0, decomp.skew[i]); | 
|  | EXPECT_EQ(0.0, decomp.quaternion[i]); | 
|  | EXPECT_EQ(0.0, decomp.perspective[i]); | 
|  | } | 
|  | EXPECT_EQ(1.0, decomp.quaternion[3]); | 
|  | EXPECT_EQ(1.0, decomp.perspective[3]); | 
|  | Transform identity; | 
|  | Transform composed = ComposeTransform(decomp); | 
|  | EXPECT_TRUE(MatricesAreNearlyEqual(identity, composed)); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, FactorTRS) { | 
|  | for (int degrees = 0; degrees < 180; ++degrees) { | 
|  | // build a transformation matrix. | 
|  | gfx::Transform transform; | 
|  | transform.Translate(degrees * 2, -degrees * 3); | 
|  | transform.Rotate(degrees); | 
|  | transform.Scale(degrees + 1, 2 * degrees + 1); | 
|  |  | 
|  | // factor the matrix | 
|  | DecomposedTransform decomp; | 
|  | bool success = DecomposeTransform(&decomp, transform); | 
|  | EXPECT_TRUE(success); | 
|  | EXPECT_FLOAT_EQ(decomp.translate[0], degrees * 2); | 
|  | EXPECT_FLOAT_EQ(decomp.translate[1], -degrees * 3); | 
|  | double rotation = | 
|  | std::acos(SkMScalarToDouble(decomp.quaternion[3])) * 360.0 / M_PI; | 
|  | while (rotation < 0.0) | 
|  | rotation += 360.0; | 
|  | while (rotation > 360.0) | 
|  | rotation -= 360.0; | 
|  |  | 
|  | const float epsilon = 0.00015f; | 
|  | EXPECT_NEAR(rotation, degrees, epsilon); | 
|  | EXPECT_NEAR(decomp.scale[0], degrees + 1, epsilon); | 
|  | EXPECT_NEAR(decomp.scale[1], 2 * degrees + 1, epsilon); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, IntegerTranslation) { | 
|  | gfx::Transform transform; | 
|  | EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation()); | 
|  |  | 
|  | transform.Translate3d(1, 2, 3); | 
|  | EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.Translate3d(-1, -2, -3); | 
|  | EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.Translate3d(4.5f, 0, 0); | 
|  | EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.Translate3d(0, -6.7f, 0); | 
|  | EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.Translate3d(0, 0, 8.9f); | 
|  | EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyMatrixInversion) { | 
|  | { | 
|  | // Invert a translation | 
|  | gfx::Transform translation; | 
|  | translation.Translate3d(2.0, 3.0, 4.0); | 
|  | EXPECT_TRUE(translation.IsInvertible()); | 
|  |  | 
|  | gfx::Transform inverse_translation; | 
|  | bool is_invertible = translation.GetInverse(&inverse_translation); | 
|  | EXPECT_TRUE(is_invertible); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, -2.0f, inverse_translation); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, -3.0f, inverse_translation); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, -4.0f, inverse_translation); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f,  1.0f, inverse_translation); | 
|  | } | 
|  |  | 
|  | { | 
|  | // Invert a non-uniform scale | 
|  | gfx::Transform scale; | 
|  | scale.Scale3d(4.0, 10.0, 100.0); | 
|  | EXPECT_TRUE(scale.IsInvertible()); | 
|  |  | 
|  | gfx::Transform inverse_scale; | 
|  | bool is_invertible = scale.GetInverse(&inverse_scale); | 
|  | EXPECT_TRUE(is_invertible); | 
|  | EXPECT_ROW1_EQ(0.25f, 0.0f, 0.0f, 0.0f, inverse_scale); | 
|  | EXPECT_ROW2_EQ(0.0f,  0.1f, 0.0f, 0.0f, inverse_scale); | 
|  | EXPECT_ROW3_EQ(0.0f,  0.0f, 0.01f, 0.0f, inverse_scale); | 
|  | EXPECT_ROW4_EQ(0.0f,  0.0f, 0.0f, 1.0f, inverse_scale); | 
|  | } | 
|  |  | 
|  | { | 
|  | // Try to invert a matrix that is not invertible. | 
|  | // The inverse() function should reset the output matrix to identity. | 
|  | gfx::Transform uninvertible; | 
|  | uninvertible.matrix().set(0, 0, 0.f); | 
|  | uninvertible.matrix().set(1, 1, 0.f); | 
|  | uninvertible.matrix().set(2, 2, 0.f); | 
|  | uninvertible.matrix().set(3, 3, 0.f); | 
|  | EXPECT_FALSE(uninvertible.IsInvertible()); | 
|  |  | 
|  | gfx::Transform inverse_of_uninvertible; | 
|  |  | 
|  | // Add a scale just to more easily ensure that inverse_of_uninvertible is | 
|  | // reset to identity. | 
|  | inverse_of_uninvertible.Scale3d(4.0, 10.0, 100.0); | 
|  |  | 
|  | bool is_invertible = uninvertible.GetInverse(&inverse_of_uninvertible); | 
|  | EXPECT_FALSE(is_invertible); | 
|  | EXPECT_TRUE(inverse_of_uninvertible.IsIdentity()); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, inverse_of_uninvertible); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, inverse_of_uninvertible); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, inverse_of_uninvertible); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, inverse_of_uninvertible); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyBackfaceVisibilityBasicCases) { | 
|  | Transform transform; | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | EXPECT_FALSE(transform.IsBackFaceVisible()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutYAxis(80.0); | 
|  | EXPECT_FALSE(transform.IsBackFaceVisible()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutYAxis(100.0); | 
|  | EXPECT_TRUE(transform.IsBackFaceVisible()); | 
|  |  | 
|  | // Edge case, 90 degree rotation should return false. | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutYAxis(90.0); | 
|  | EXPECT_FALSE(transform.IsBackFaceVisible()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyBackfaceVisibilityForPerspective) { | 
|  | Transform layer_space_to_projection_plane; | 
|  |  | 
|  | // This tests if IsBackFaceVisible works properly under perspective | 
|  | // transforms.  Specifically, layers that may have their back face visible in | 
|  | // orthographic projection, may not actually have back face visible under | 
|  | // perspective projection. | 
|  |  | 
|  | // Case 1: Layer is rotated by slightly more than 90 degrees, at the center | 
|  | //         of the prespective projection. In this case, the layer's back-side | 
|  | //         is visible to the camera. | 
|  | layer_space_to_projection_plane.MakeIdentity(); | 
|  | layer_space_to_projection_plane.ApplyPerspectiveDepth(1.0); | 
|  | layer_space_to_projection_plane.Translate3d(0.0, 0.0, 0.0); | 
|  | layer_space_to_projection_plane.RotateAboutYAxis(100.0); | 
|  | EXPECT_TRUE(layer_space_to_projection_plane.IsBackFaceVisible()); | 
|  |  | 
|  | // Case 2: Layer is rotated by slightly more than 90 degrees, but shifted off | 
|  | //         to the side of the camera. Because of the wide field-of-view, the | 
|  | //         layer's front side is still visible. | 
|  | // | 
|  | //                       |<-- front side of layer is visible to camera | 
|  | //                    \  |            / | 
|  | //                     \ |           / | 
|  | //                      \|          / | 
|  | //                       |         / | 
|  | //                       |\       /<-- camera field of view | 
|  | //                       | \     / | 
|  | // back side of layer -->|  \   / | 
|  | //                           \./ <-- camera origin | 
|  | // | 
|  | layer_space_to_projection_plane.MakeIdentity(); | 
|  | layer_space_to_projection_plane.ApplyPerspectiveDepth(1.0); | 
|  | layer_space_to_projection_plane.Translate3d(-10.0, 0.0, 0.0); | 
|  | layer_space_to_projection_plane.RotateAboutYAxis(100.0); | 
|  | EXPECT_FALSE(layer_space_to_projection_plane.IsBackFaceVisible()); | 
|  |  | 
|  | // Case 3: Additionally rotating the layer by 180 degrees should of course | 
|  | //         show the opposite result of case 2. | 
|  | layer_space_to_projection_plane.RotateAboutYAxis(180.0); | 
|  | EXPECT_TRUE(layer_space_to_projection_plane.IsBackFaceVisible()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyDefaultConstructorCreatesIdentityMatrix) { | 
|  | Transform A; | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  | EXPECT_TRUE(A.IsIdentity()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyCopyConstructor) { | 
|  | Transform A; | 
|  | InitializeTestMatrix(&A); | 
|  |  | 
|  | // Copy constructor should produce exact same elements as matrix A. | 
|  | Transform B(A); | 
|  | EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, B); | 
|  | EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, B); | 
|  | EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, B); | 
|  | EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, B); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyConstructorFor16Elements) { | 
|  | Transform transform(1.0, 2.0, 3.0, 4.0, | 
|  | 5.0, 6.0, 7.0, 8.0, | 
|  | 9.0, 10.0, 11.0, 12.0, | 
|  | 13.0, 14.0, 15.0, 16.0); | 
|  |  | 
|  | EXPECT_ROW1_EQ(1.0f, 2.0f, 3.0f, 4.0f, transform); | 
|  | EXPECT_ROW2_EQ(5.0f, 6.0f, 7.0f, 8.0f, transform); | 
|  | EXPECT_ROW3_EQ(9.0f, 10.0f, 11.0f, 12.0f, transform); | 
|  | EXPECT_ROW4_EQ(13.0f, 14.0f, 15.0f, 16.0f, transform); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyConstructorFor2dElements) { | 
|  | Transform transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); | 
|  |  | 
|  | EXPECT_ROW1_EQ(1.0f, 2.0f, 0.0f, 5.0f, transform); | 
|  | EXPECT_ROW2_EQ(3.0f, 4.0f, 0.0f, 6.0f, transform); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, transform); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, transform); | 
|  | } | 
|  |  | 
|  |  | 
|  | TEST(XFormTest, verifyAssignmentOperator) { | 
|  | Transform A; | 
|  | InitializeTestMatrix(&A); | 
|  | Transform B; | 
|  | InitializeTestMatrix2(&B); | 
|  | 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.0f, 14.0f, 18.0f, 22.0f, B); | 
|  | EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, B); | 
|  | EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, B); | 
|  | EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, B); | 
|  |  | 
|  | EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, C); | 
|  | EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, C); | 
|  | EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, C); | 
|  | EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, C); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyEqualsBooleanOperator) { | 
|  | Transform A; | 
|  | InitializeTestMatrix(&A); | 
|  |  | 
|  | Transform B; | 
|  | InitializeTestMatrix(&B); | 
|  | EXPECT_TRUE(A == B); | 
|  |  | 
|  | // Modifying multiple elements should cause equals operator to return false. | 
|  | Transform C; | 
|  | InitializeTestMatrix2(&C); | 
|  | EXPECT_FALSE(A == C); | 
|  |  | 
|  | // Modifying any one individual element should cause equals operator to | 
|  | // return false. | 
|  | Transform D; | 
|  | D = A; | 
|  | D.matrix().set(0, 0, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(1, 0, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(2, 0, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(3, 0, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(0, 1, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(1, 1, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(2, 1, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(3, 1, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(0, 2, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(1, 2, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(2, 2, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(3, 2, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(0, 3, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(1, 3, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(2, 3, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  |  | 
|  | D = A; | 
|  | D.matrix().set(3, 3, 0.f); | 
|  | EXPECT_FALSE(A == D); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyMultiplyOperator) { | 
|  | Transform A; | 
|  | InitializeTestMatrix(&A); | 
|  |  | 
|  | Transform B; | 
|  | InitializeTestMatrix2(&B); | 
|  |  | 
|  | Transform C = A * B; | 
|  | EXPECT_ROW1_EQ(2036.0f, 2292.0f, 2548.0f, 2804.0f, C); | 
|  | EXPECT_ROW2_EQ(2162.0f, 2434.0f, 2706.0f, 2978.0f, C); | 
|  | EXPECT_ROW3_EQ(2288.0f, 2576.0f, 2864.0f, 3152.0f, C); | 
|  | EXPECT_ROW4_EQ(2414.0f, 2718.0f, 3022.0f, 3326.0f, C); | 
|  |  | 
|  | // Just an additional sanity check; matrix multiplication is not commutative. | 
|  | EXPECT_FALSE(A * B == B * A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyMultiplyAndAssignOperator) { | 
|  | Transform A; | 
|  | InitializeTestMatrix(&A); | 
|  |  | 
|  | Transform B; | 
|  | InitializeTestMatrix2(&B); | 
|  |  | 
|  | A *= B; | 
|  | EXPECT_ROW1_EQ(2036.0f, 2292.0f, 2548.0f, 2804.0f, A); | 
|  | EXPECT_ROW2_EQ(2162.0f, 2434.0f, 2706.0f, 2978.0f, A); | 
|  | EXPECT_ROW3_EQ(2288.0f, 2576.0f, 2864.0f, 3152.0f, A); | 
|  | EXPECT_ROW4_EQ(2414.0f, 2718.0f, 3022.0f, 3326.0f, A); | 
|  |  | 
|  | // Just an additional sanity check; matrix multiplication is not commutative. | 
|  | Transform C = A; | 
|  | C *= B; | 
|  | Transform D = B; | 
|  | D *= A; | 
|  | EXPECT_FALSE(C == D); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyMatrixMultiplication) { | 
|  | Transform A; | 
|  | InitializeTestMatrix(&A); | 
|  |  | 
|  | Transform B; | 
|  | InitializeTestMatrix2(&B); | 
|  |  | 
|  | A.PreconcatTransform(B); | 
|  | EXPECT_ROW1_EQ(2036.0f, 2292.0f, 2548.0f, 2804.0f, A); | 
|  | EXPECT_ROW2_EQ(2162.0f, 2434.0f, 2706.0f, 2978.0f, A); | 
|  | EXPECT_ROW3_EQ(2288.0f, 2576.0f, 2864.0f, 3152.0f, A); | 
|  | EXPECT_ROW4_EQ(2414.0f, 2718.0f, 3022.0f, 3326.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyMakeIdentiy) { | 
|  | Transform A; | 
|  | InitializeTestMatrix(&A); | 
|  | A.MakeIdentity(); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  | EXPECT_TRUE(A.IsIdentity()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyTranslate) { | 
|  | Transform A; | 
|  | A.Translate(2.0, 3.0); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 2.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 3.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | // Verify that Translate() post-multiplies the existing matrix. | 
|  | A.MakeIdentity(); | 
|  | A.Scale(5.0, 5.0); | 
|  | A.Translate(2.0, 3.0); | 
|  | EXPECT_ROW1_EQ(5.0f, 0.0f, 0.0f, 10.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 5.0f, 0.0f, 15.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f,  A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f,  A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyTranslate3d) { | 
|  | Transform A; | 
|  | A.Translate3d(2.0, 3.0, 4.0); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 2.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 3.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 4.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | // Verify that Translate3d() post-multiplies the existing matrix. | 
|  | A.MakeIdentity(); | 
|  | A.Scale3d(6.0, 7.0, 8.0); | 
|  | A.Translate3d(2.0, 3.0, 4.0); | 
|  | EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 12.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 21.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 32.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f,  A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyScale) { | 
|  | Transform A; | 
|  | A.Scale(6.0, 7.0); | 
|  | EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | // Verify that Scale() post-multiplies the existing matrix. | 
|  | A.MakeIdentity(); | 
|  | A.Translate3d(2.0, 3.0, 4.0); | 
|  | A.Scale(6.0, 7.0); | 
|  | EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 2.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 3.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 4.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyScale3d) { | 
|  | Transform A; | 
|  | A.Scale3d(6.0, 7.0, 8.0); | 
|  | EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | // Verify that scale3d() post-multiplies the existing matrix. | 
|  | A.MakeIdentity(); | 
|  | A.Translate3d(2.0, 3.0, 4.0); | 
|  | A.Scale3d(6.0, 7.0, 8.0); | 
|  | EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 2.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 3.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 4.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyRotate) { | 
|  | Transform A; | 
|  | A.Rotate(90.0); | 
|  | EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | // Verify that Rotate() post-multiplies the existing matrix. | 
|  | A.MakeIdentity(); | 
|  | A.Scale3d(6.0, 7.0, 8.0); | 
|  | A.Rotate(90.0); | 
|  | EXPECT_ROW1_NEAR(0.0, -6.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(7.0, 0.0,  0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyRotateAboutXAxis) { | 
|  | Transform A; | 
|  | double sin45 = 0.5 * sqrt(2.0); | 
|  | double cos45 = sin45; | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.RotateAboutXAxis(90.0); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW2_NEAR(0.0, 0.0, -1.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(0.0, 1.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.RotateAboutXAxis(45.0); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW2_NEAR(0.0, cos45, -sin45, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(0.0, sin45, cos45, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | // Verify that RotateAboutXAxis(angle) post-multiplies the existing matrix. | 
|  | A.MakeIdentity(); | 
|  | A.Scale3d(6.0, 7.0, 8.0); | 
|  | A.RotateAboutXAxis(90.0); | 
|  | EXPECT_ROW1_NEAR(6.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(0.0, 0.0, -7.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(0.0, 8.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyRotateAboutYAxis) { | 
|  | Transform A; | 
|  | double sin45 = 0.5 * sqrt(2.0); | 
|  | double cos45 = sin45; | 
|  |  | 
|  | // Note carefully, the expected pattern is inverted compared to rotating | 
|  | // about x axis or z axis. | 
|  | A.MakeIdentity(); | 
|  | A.RotateAboutYAxis(90.0); | 
|  | EXPECT_ROW1_NEAR(0.0, 0.0, 1.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW3_NEAR(-1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.RotateAboutYAxis(45.0); | 
|  | EXPECT_ROW1_NEAR(cos45, 0.0, sin45, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW3_NEAR(-sin45, 0.0, cos45, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | // Verify that RotateAboutYAxis(angle) post-multiplies the existing matrix. | 
|  | A.MakeIdentity(); | 
|  | A.Scale3d(6.0, 7.0, 8.0); | 
|  | A.RotateAboutYAxis(90.0); | 
|  | EXPECT_ROW1_NEAR(0.0, 0.0, 6.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(0.0, 7.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(-8.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyRotateAboutZAxis) { | 
|  | Transform A; | 
|  | double sin45 = 0.5 * sqrt(2.0); | 
|  | double cos45 = sin45; | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.RotateAboutZAxis(90.0); | 
|  | EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.RotateAboutZAxis(45.0); | 
|  | EXPECT_ROW1_NEAR(cos45, -sin45, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(sin45, cos45, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | // Verify that RotateAboutZAxis(angle) post-multiplies the existing matrix. | 
|  | A.MakeIdentity(); | 
|  | A.Scale3d(6.0, 7.0, 8.0); | 
|  | A.RotateAboutZAxis(90.0); | 
|  | EXPECT_ROW1_NEAR(0.0, -6.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(7.0, 0.0,  0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyRotateAboutForAlignedAxes) { | 
|  | Transform A; | 
|  |  | 
|  | // Check rotation about z-axis | 
|  | A.MakeIdentity(); | 
|  | A.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0); | 
|  | EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | // Check rotation about x-axis | 
|  | A.MakeIdentity(); | 
|  | A.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW2_NEAR(0.0, 0.0, -1.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(0.0, 1.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, 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.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0); | 
|  | EXPECT_ROW1_NEAR(0.0, 0.0, 1.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW3_NEAR(-1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  |  | 
|  | // Verify that rotate3d(axis, angle) post-multiplies the existing matrix. | 
|  | A.MakeIdentity(); | 
|  | A.Scale3d(6.0, 7.0, 8.0); | 
|  | A.RotateAboutZAxis(90.0); | 
|  | EXPECT_ROW1_NEAR(0.0, -6.0, 0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(7.0, 0.0,  0.0, 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyRotateAboutForArbitraryAxis) { | 
|  | // Check rotation about an arbitrary non-axis-aligned vector. | 
|  | Transform A; | 
|  | A.RotateAbout(Vector3dF(1.0, 1.0, 1.0), 90.0); | 
|  | EXPECT_ROW1_NEAR(0.3333333333333334258519187, | 
|  | -0.2440169358562924717404030, | 
|  | 0.9106836025229592124219380, | 
|  | 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW2_NEAR(0.9106836025229592124219380, | 
|  | 0.3333333333333334258519187, | 
|  | -0.2440169358562924717404030, | 
|  | 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW3_NEAR(-0.2440169358562924717404030, | 
|  | 0.9106836025229592124219380, | 
|  | 0.3333333333333334258519187, | 
|  | 0.0, A, ERROR_THRESHOLD); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyRotateAboutForDegenerateAxis) { | 
|  | // Check rotation about a degenerate zero vector. | 
|  | // It is expected to skip applying the rotation. | 
|  | Transform A; | 
|  |  | 
|  | A.RotateAbout(Vector3dF(0.0, 0.0, 0.0), 45.0); | 
|  | // Verify that A remains unchanged. | 
|  | EXPECT_TRUE(A.IsIdentity()); | 
|  |  | 
|  | InitializeTestMatrix(&A); | 
|  | A.RotateAbout(Vector3dF(0.0, 0.0, 0.0), 35.0); | 
|  |  | 
|  | // Verify that A remains unchanged. | 
|  | EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, A); | 
|  | EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, A); | 
|  | EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, A); | 
|  | EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifySkewX) { | 
|  | Transform A; | 
|  | A.SkewX(45.0); | 
|  | EXPECT_ROW1_EQ(1.0f, 1.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, 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.0, 7.0, 8.0); | 
|  | A.SkewX(45.0); | 
|  | EXPECT_ROW1_EQ(6.0f, 6.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifySkewY) { | 
|  | Transform A; | 
|  | A.SkewY(45.0); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW2_EQ(1.0f, 1.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, 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.0, 7.0, 8.0); | 
|  | A.SkewY(45.0); | 
|  | EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW2_EQ(7.0f, 7.0f, 0.0f, 0.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyPerspectiveDepth) { | 
|  | Transform A; | 
|  | A.ApplyPerspectiveDepth(1.0); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f,  0.0f, 0.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f,  0.0f, 0.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f,  1.0f, 0.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, -1.0f, 1.0f, A); | 
|  |  | 
|  | // Verify that PerspectiveDepth() post-multiplies the existing matrix. | 
|  | A.MakeIdentity(); | 
|  | A.Translate3d(2.0, 3.0, 4.0); | 
|  | A.ApplyPerspectiveDepth(1.0); | 
|  | EXPECT_ROW1_EQ(1.0f, 0.0f, -2.0f, 2.0f, A); | 
|  | EXPECT_ROW2_EQ(0.0f, 1.0f, -3.0f, 3.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f, 0.0f, -3.0f, 4.0f, A); | 
|  | EXPECT_ROW4_EQ(0.0f, 0.0f, -1.0f, 1.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyHasPerspective) { | 
|  | Transform A; | 
|  | A.ApplyPerspectiveDepth(1.0); | 
|  | EXPECT_TRUE(A.HasPerspective()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.ApplyPerspectiveDepth(0.0); | 
|  | EXPECT_FALSE(A.HasPerspective()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 0, -1.f); | 
|  | EXPECT_TRUE(A.HasPerspective()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 1, -1.f); | 
|  | EXPECT_TRUE(A.HasPerspective()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 2, -0.3f); | 
|  | EXPECT_TRUE(A.HasPerspective()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 3, 0.5f); | 
|  | EXPECT_TRUE(A.HasPerspective()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 3, 0.f); | 
|  | EXPECT_TRUE(A.HasPerspective()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyIsInvertible) { | 
|  | Transform A; | 
|  |  | 
|  | // Translations, rotations, scales, skews and arbitrary combinations of them | 
|  | // are invertible. | 
|  | A.MakeIdentity(); | 
|  | EXPECT_TRUE(A.IsInvertible()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.Translate3d(2.0, 3.0, 4.0); | 
|  | EXPECT_TRUE(A.IsInvertible()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.Scale3d(6.0, 7.0, 8.0); | 
|  | EXPECT_TRUE(A.IsInvertible()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.RotateAboutXAxis(10.0); | 
|  | A.RotateAboutYAxis(20.0); | 
|  | A.RotateAboutZAxis(30.0); | 
|  | EXPECT_TRUE(A.IsInvertible()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.SkewX(45.0); | 
|  | 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.ApplyPerspectiveDepth(1.0); | 
|  | 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.ApplyPerspectiveDepth(1.0); | 
|  | A.matrix().set(3, 3, 0.f); | 
|  | EXPECT_FALSE(A.IsInvertible()); | 
|  |  | 
|  | // Adding more to a non-invertible matrix will not make it invertible in the | 
|  | // general case. | 
|  | A.MakeIdentity(); | 
|  | A.ApplyPerspectiveDepth(1.0); | 
|  | A.matrix().set(3, 3, 0.f); | 
|  | A.Scale3d(6.0, 7.0, 8.0); | 
|  | A.RotateAboutXAxis(10.0); | 
|  | A.RotateAboutYAxis(20.0); | 
|  | A.RotateAboutZAxis(30.0); | 
|  | A.Translate3d(6.0, 7.0, 8.0); | 
|  | EXPECT_FALSE(A.IsInvertible()); | 
|  |  | 
|  | // A degenerate matrix of all zeros is not invertible. | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(0, 0, 0.f); | 
|  | A.matrix().set(1, 1, 0.f); | 
|  | A.matrix().set(2, 2, 0.f); | 
|  | A.matrix().set(3, 3, 0.f); | 
|  | EXPECT_FALSE(A.IsInvertible()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyIsIdentity) { | 
|  | Transform 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.matrix().set(0, 0, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(1, 0, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(2, 0, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 0, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(0, 1, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(1, 1, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(2, 1, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 1, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(0, 2, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(1, 2, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(2, 2, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 2, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(0, 3, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(1, 3, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(2, 3, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 3, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentity()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyIsIdentityOrTranslation) { | 
|  | Transform 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: (0, 3), (1, 3), and | 
|  | // (2, 3) are the translation components, so modifying them should still | 
|  | // return true. | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(0, 0, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(1, 0, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(2, 0, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 0, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(0, 1, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(1, 1, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(2, 1, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 1, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(0, 2, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(1, 2, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(2, 2, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 2, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | // Note carefully - expecting true here. | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(0, 3, 2.f); | 
|  | EXPECT_TRUE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | // Note carefully - expecting true here. | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(1, 3, 2.f); | 
|  | EXPECT_TRUE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | // Note carefully - expecting true here. | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(2, 3, 2.f); | 
|  | EXPECT_TRUE(A.IsIdentityOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 3, 2.f); | 
|  | EXPECT_FALSE(A.IsIdentityOrTranslation()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyIsApproximatelyIdentityOrTranslation) { | 
|  | Transform A; | 
|  | SkMatrix44& matrix = A.matrix(); | 
|  |  | 
|  | // Exact pure translation. | 
|  | A.MakeIdentity(); | 
|  |  | 
|  | // Set translate values to values other than 0 or 1. | 
|  | matrix.set(0, 3, 3.4f); | 
|  | matrix.set(1, 3, 4.4f); | 
|  | matrix.set(2, 3, 5.6f); | 
|  |  | 
|  | EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(0)); | 
|  | EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); | 
|  |  | 
|  | // Approximately pure translation. | 
|  | InitializeApproxIdentityMatrix(&A); | 
|  |  | 
|  | // Some values must be exact. | 
|  | matrix.set(3, 0, 0); | 
|  | matrix.set(3, 1, 0); | 
|  | matrix.set(3, 2, 0); | 
|  | matrix.set(3, 3, 1); | 
|  |  | 
|  | // Set translate values to values other than 0 or 1. | 
|  | matrix.set(0, 3, 3.4f); | 
|  | matrix.set(1, 3, 4.4f); | 
|  | matrix.set(2, 3, 5.6f); | 
|  |  | 
|  | EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0)); | 
|  | EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); | 
|  |  | 
|  | // Not approximately pure translation. | 
|  | InitializeApproxIdentityMatrix(&A); | 
|  |  | 
|  | // Some values must be exact. | 
|  | matrix.set(3, 0, 0); | 
|  | matrix.set(3, 1, 0); | 
|  | matrix.set(3, 2, 0); | 
|  | matrix.set(3, 3, 1); | 
|  |  | 
|  | // Set some values (not translate values) to values other than 0 or 1. | 
|  | matrix.set(0, 1, 3.4f); | 
|  | matrix.set(3, 2, 4.4f); | 
|  | matrix.set(2, 0, 5.6f); | 
|  |  | 
|  | EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0)); | 
|  | EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyIsScaleOrTranslation) { | 
|  | Transform A; | 
|  |  | 
|  | InitializeTestMatrix(&A); | 
|  | EXPECT_FALSE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | EXPECT_TRUE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | // Modifying any non-scale or non-translation components should cause | 
|  | // IsScaleOrTranslation() to return false. (0, 0), (1, 1), (2, 2), (0, 3), | 
|  | // (1, 3), and (2, 3) are the scale and translation components, so | 
|  | // modifying them should still return true. | 
|  |  | 
|  | // Note carefully - expecting true here. | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(0, 0, 2.f); | 
|  | EXPECT_TRUE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(1, 0, 2.f); | 
|  | EXPECT_FALSE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(2, 0, 2.f); | 
|  | EXPECT_FALSE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 0, 2.f); | 
|  | EXPECT_FALSE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(0, 1, 2.f); | 
|  | EXPECT_FALSE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | // Note carefully - expecting true here. | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(1, 1, 2.f); | 
|  | EXPECT_TRUE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(2, 1, 2.f); | 
|  | EXPECT_FALSE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 1, 2.f); | 
|  | EXPECT_FALSE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(0, 2, 2.f); | 
|  | EXPECT_FALSE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(1, 2, 2.f); | 
|  | EXPECT_FALSE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | // Note carefully - expecting true here. | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(2, 2, 2.f); | 
|  | EXPECT_TRUE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 2, 2.f); | 
|  | EXPECT_FALSE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | // Note carefully - expecting true here. | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(0, 3, 2.f); | 
|  | EXPECT_TRUE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | // Note carefully - expecting true here. | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(1, 3, 2.f); | 
|  | EXPECT_TRUE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | // Note carefully - expecting true here. | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(2, 3, 2.f); | 
|  | EXPECT_TRUE(A.IsScaleOrTranslation()); | 
|  |  | 
|  | A.MakeIdentity(); | 
|  | A.matrix().set(3, 3, 2.f); | 
|  | EXPECT_FALSE(A.IsScaleOrTranslation()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, verifyFlattenTo2d) { | 
|  | Transform A; | 
|  | InitializeTestMatrix(&A); | 
|  |  | 
|  | A.FlattenTo2d(); | 
|  | EXPECT_ROW1_EQ(10.0f, 14.0f, 0.0f, 22.0f, A); | 
|  | EXPECT_ROW2_EQ(11.0f, 15.0f, 0.0f, 23.0f, A); | 
|  | EXPECT_ROW3_EQ(0.0f,  0.0f,  1.0f, 0.0f,  A); | 
|  | EXPECT_ROW4_EQ(13.0f, 17.0f, 0.0f, 25.0f, A); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, IsFlat) { | 
|  | Transform transform; | 
|  | InitializeTestMatrix(&transform); | 
|  |  | 
|  | // A transform with all entries non-zero isn't flat. | 
|  | EXPECT_FALSE(transform.IsFlat()); | 
|  |  | 
|  | transform.matrix().set(0, 2, 0.f); | 
|  | transform.matrix().set(1, 2, 0.f); | 
|  | transform.matrix().set(2, 2, 1.f); | 
|  | transform.matrix().set(3, 2, 0.f); | 
|  |  | 
|  | EXPECT_FALSE(transform.IsFlat()); | 
|  |  | 
|  | transform.matrix().set(2, 0, 0.f); | 
|  | transform.matrix().set(2, 1, 0.f); | 
|  | transform.matrix().set(2, 3, 0.f); | 
|  |  | 
|  | // Since the third column and row are both (0, 0, 1, 0), the transform is | 
|  | // flat. | 
|  | EXPECT_TRUE(transform.IsFlat()); | 
|  | } | 
|  |  | 
|  | // Another implementation of Preserves2dAxisAlignment that isn't as fast, | 
|  | // good for testing the faster implementation. | 
|  | static bool EmpiricallyPreserves2dAxisAlignment(const Transform& transform) { | 
|  | Point3F p1(5.0f, 5.0f, 0.0f); | 
|  | Point3F p2(10.0f, 5.0f, 0.0f); | 
|  | Point3F p3(10.0f, 20.0f, 0.0f); | 
|  | Point3F p4(5.0f, 20.0f, 0.0f); | 
|  |  | 
|  | QuadF test_quad(PointF(p1.x(), p1.y()), | 
|  | PointF(p2.x(), p2.y()), | 
|  | PointF(p3.x(), p3.y()), | 
|  | PointF(p4.x(), p4.y())); | 
|  | EXPECT_TRUE(test_quad.IsRectilinear()); | 
|  |  | 
|  | transform.TransformPoint(&p1); | 
|  | transform.TransformPoint(&p2); | 
|  | transform.TransformPoint(&p3); | 
|  | transform.TransformPoint(&p4); | 
|  |  | 
|  | QuadF transformedQuad(PointF(p1.x(), p1.y()), | 
|  | PointF(p2.x(), p2.y()), | 
|  | PointF(p3.x(), p3.y()), | 
|  | PointF(p4.x(), p4.y())); | 
|  | return transformedQuad.IsRectilinear(); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, Preserves2dAxisAlignment) { | 
|  | static const struct TestCase { | 
|  | SkMScalar a; // row 1, column 1 | 
|  | SkMScalar b; // row 1, column 2 | 
|  | SkMScalar c; // row 2, column 1 | 
|  | SkMScalar d; // row 2, column 2 | 
|  | bool expected; | 
|  | } test_cases[] = { | 
|  | { 3.f, 0.f, | 
|  | 0.f, 4.f, true }, // basic case | 
|  | { 0.f, 4.f, | 
|  | 3.f, 0.f, true }, // rotate by 90 | 
|  | { 0.f, 0.f, | 
|  | 0.f, 4.f, true }, // degenerate x | 
|  | { 3.f, 0.f, | 
|  | 0.f, 0.f, true }, // degenerate y | 
|  | { 0.f, 0.f, | 
|  | 3.f, 0.f, true }, // degenerate x + rotate by 90 | 
|  | { 0.f, 4.f, | 
|  | 0.f, 0.f, true }, // degenerate y + rotate by 90 | 
|  | { 3.f, 4.f, | 
|  | 0.f, 0.f, false }, | 
|  | { 0.f, 0.f, | 
|  | 3.f, 4.f, false }, | 
|  | { 0.f, 3.f, | 
|  | 0.f, 4.f, false }, | 
|  | { 3.f, 0.f, | 
|  | 4.f, 0.f, false }, | 
|  | { 3.f, 4.f, | 
|  | 5.f, 0.f, false }, | 
|  | { 3.f, 4.f, | 
|  | 0.f, 5.f, false }, | 
|  | { 3.f, 0.f, | 
|  | 4.f, 5.f, false }, | 
|  | { 0.f, 3.f, | 
|  | 4.f, 5.f, false }, | 
|  | { 2.f, 3.f, | 
|  | 4.f, 5.f, false }, | 
|  | }; | 
|  |  | 
|  | Transform transform; | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | transform.MakeIdentity(); | 
|  | transform.matrix().set(0, 0, value.a); | 
|  | transform.matrix().set(0, 1, value.b); | 
|  | transform.matrix().set(1, 0, value.c); | 
|  | transform.matrix().set(1, 1, value.d); | 
|  |  | 
|  | if (value.expected) { | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  | } else { | 
|  | EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_FALSE(transform.Preserves2dAxisAlignment()); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Try the same test cases again, but this time make sure that other matrix | 
|  | // elements (except perspective) have entries, to test that they are ignored. | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | transform.MakeIdentity(); | 
|  | transform.matrix().set(0, 0, value.a); | 
|  | transform.matrix().set(0, 1, value.b); | 
|  | transform.matrix().set(1, 0, value.c); | 
|  | transform.matrix().set(1, 1, value.d); | 
|  |  | 
|  | transform.matrix().set(0, 2, 1.f); | 
|  | transform.matrix().set(0, 3, 2.f); | 
|  | transform.matrix().set(1, 2, 3.f); | 
|  | transform.matrix().set(1, 3, 4.f); | 
|  | transform.matrix().set(2, 0, 5.f); | 
|  | transform.matrix().set(2, 1, 6.f); | 
|  | transform.matrix().set(2, 2, 7.f); | 
|  | transform.matrix().set(2, 3, 8.f); | 
|  |  | 
|  | if (value.expected) { | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  | } else { | 
|  | EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_FALSE(transform.Preserves2dAxisAlignment()); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Try the same test cases again, but this time add perspective which is | 
|  | // always assumed to not-preserve axis alignment. | 
|  | for (size_t i = 0; i < arraysize(test_cases); ++i) { | 
|  | const TestCase& value = test_cases[i]; | 
|  | transform.MakeIdentity(); | 
|  | transform.matrix().set(0, 0, value.a); | 
|  | transform.matrix().set(0, 1, value.b); | 
|  | transform.matrix().set(1, 0, value.c); | 
|  | transform.matrix().set(1, 1, value.d); | 
|  |  | 
|  | transform.matrix().set(0, 2, 1.f); | 
|  | transform.matrix().set(0, 3, 2.f); | 
|  | transform.matrix().set(1, 2, 3.f); | 
|  | transform.matrix().set(1, 3, 4.f); | 
|  | transform.matrix().set(2, 0, 5.f); | 
|  | transform.matrix().set(2, 1, 6.f); | 
|  | transform.matrix().set(2, 2, 7.f); | 
|  | transform.matrix().set(2, 3, 8.f); | 
|  | transform.matrix().set(3, 0, 9.f); | 
|  | transform.matrix().set(3, 1, 10.f); | 
|  | transform.matrix().set(3, 2, 11.f); | 
|  | transform.matrix().set(3, 3, 12.f); | 
|  |  | 
|  | EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_FALSE(transform.Preserves2dAxisAlignment()); | 
|  | } | 
|  |  | 
|  | // Try a few more practical situations to check precision | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutZAxis(90.0); | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutZAxis(180.0); | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutZAxis(270.0); | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutYAxis(90.0); | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutXAxis(90.0); | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutZAxis(90.0); | 
|  | transform.RotateAboutYAxis(90.0); | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutZAxis(90.0); | 
|  | transform.RotateAboutXAxis(90.0); | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutYAxis(90.0); | 
|  | transform.RotateAboutZAxis(90.0); | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutZAxis(45.0); | 
|  | EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_FALSE(transform.Preserves2dAxisAlignment()); | 
|  |  | 
|  | // 3-d case; In 2d after an orthographic projection, this case does | 
|  | // preserve 2d axis alignment. But in 3d, it does not preserve axis | 
|  | // alignment. | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutYAxis(45.0); | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.RotateAboutXAxis(45.0); | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  |  | 
|  | // Perspective cases. | 
|  | transform.MakeIdentity(); | 
|  | transform.ApplyPerspectiveDepth(10.0); | 
|  | transform.RotateAboutYAxis(45.0); | 
|  | EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_FALSE(transform.Preserves2dAxisAlignment()); | 
|  |  | 
|  | transform.MakeIdentity(); | 
|  | transform.ApplyPerspectiveDepth(10.0); | 
|  | transform.RotateAboutZAxis(90.0); | 
|  | EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); | 
|  | EXPECT_TRUE(transform.Preserves2dAxisAlignment()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, To2dTranslation) { | 
|  | Vector2dF translation(3.f, 7.f); | 
|  | Transform transform; | 
|  | transform.Translate(translation.x(), translation.y() + 1); | 
|  | EXPECT_NE(translation.ToString(), transform.To2dTranslation().ToString()); | 
|  | transform.MakeIdentity(); | 
|  | transform.Translate(translation.x(), translation.y()); | 
|  | EXPECT_EQ(translation.ToString(), transform.To2dTranslation().ToString()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, TransformRect) { | 
|  | Transform translation; | 
|  | translation.Translate(3.f, 7.f); | 
|  | RectF rect(1.f, 2.f, 3.f, 4.f); | 
|  | RectF expected(4.f, 9.f, 3.f, 4.f); | 
|  | translation.TransformRect(&rect); | 
|  | EXPECT_EQ(expected.ToString(), rect.ToString()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, TransformRectReverse) { | 
|  | Transform translation; | 
|  | translation.Translate(3.f, 7.f); | 
|  | RectF rect(1.f, 2.f, 3.f, 4.f); | 
|  | RectF expected(-2.f, -5.f, 3.f, 4.f); | 
|  | EXPECT_TRUE(translation.TransformRectReverse(&rect)); | 
|  | EXPECT_EQ(expected.ToString(), rect.ToString()); | 
|  |  | 
|  | Transform singular; | 
|  | singular.Scale3d(0.f, 0.f, 0.f); | 
|  | EXPECT_FALSE(singular.TransformRectReverse(&rect)); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, TransformBox) { | 
|  | Transform translation; | 
|  | translation.Translate3d(3.f, 7.f, 6.f); | 
|  | BoxF box(1.f, 2.f, 3.f, 4.f, 5.f, 6.f); | 
|  | BoxF expected(4.f, 9.f, 9.f, 4.f, 5.f, 6.f); | 
|  | translation.TransformBox(&box); | 
|  | EXPECT_EQ(expected.ToString(), box.ToString()); | 
|  | } | 
|  |  | 
|  | TEST(XFormTest, TransformBoxReverse) { | 
|  | Transform translation; | 
|  | translation.Translate3d(3.f, 7.f, 6.f); | 
|  | BoxF box(1.f, 2.f, 3.f, 4.f, 5.f, 6.f); | 
|  | BoxF expected(-2.f, -5.f, -3.f, 4.f, 5.f, 6.f); | 
|  | EXPECT_TRUE(translation.TransformBoxReverse(&box)); | 
|  | EXPECT_EQ(expected.ToString(), box.ToString()); | 
|  |  | 
|  | Transform singular; | 
|  | singular.Scale3d(0.f, 0.f, 0.f); | 
|  | EXPECT_FALSE(singular.TransformBoxReverse(&box)); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | }  // namespace gfx |