1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #define _USE_MATH_DEFINES // For VC++ to get M_PI. This has to be first.
6
7 #include <cmath>
8
9 #include "base/macros.h"
10 #include "testing/gtest/include/gtest/gtest.h"
11 #include "ui/gfx/geometry/quaternion.h"
12 #include "ui/gfx/geometry/vector3d_f.h"
13
14 namespace gfx {
15
16 namespace {
17
18 const double kEpsilon = 1e-7;
19
CompareQuaternions(const Quaternion & a,const Quaternion & b)20 void CompareQuaternions(const Quaternion& a, const Quaternion& b) {
21 EXPECT_FLOAT_EQ(a.x(), b.x());
22 EXPECT_FLOAT_EQ(a.y(), b.y());
23 EXPECT_FLOAT_EQ(a.z(), b.z());
24 EXPECT_FLOAT_EQ(a.w(), b.w());
25 }
26
27 } // namespace
28
TEST(QuatTest,DefaultConstruction)29 TEST(QuatTest, DefaultConstruction) {
30 CompareQuaternions(Quaternion(0, 0, 0, 1), Quaternion());
31 }
32
TEST(QuatTest,AxisAngleCommon)33 TEST(QuatTest, AxisAngleCommon) {
34 double radians = 0.5;
35 Quaternion q(Vector3dF(1, 0, 0), radians);
36 CompareQuaternions(
37 Quaternion(std::sin(radians / 2), 0, 0, std::cos(radians / 2)), q);
38 }
39
TEST(QuatTest,VectorToVectorRotation)40 TEST(QuatTest, VectorToVectorRotation) {
41 Quaternion q(Vector3dF(1.0f, 0.0f, 0.0f), Vector3dF(0.0f, 1.0f, 0.0f));
42 Quaternion r(Vector3dF(0.0f, 0.0f, 1.0f), M_PI_2);
43
44 EXPECT_FLOAT_EQ(r.x(), q.x());
45 EXPECT_FLOAT_EQ(r.y(), q.y());
46 EXPECT_FLOAT_EQ(r.z(), q.z());
47 EXPECT_FLOAT_EQ(r.w(), q.w());
48 }
49
TEST(QuatTest,AxisAngleWithZeroLengthAxis)50 TEST(QuatTest, AxisAngleWithZeroLengthAxis) {
51 Quaternion q(Vector3dF(0, 0, 0), 0.5);
52 // If the axis of zero length, we should assume the default values.
53 CompareQuaternions(q, Quaternion());
54 }
55
TEST(QuatTest,Addition)56 TEST(QuatTest, Addition) {
57 double values[] = {0, 1, 100};
58 for (size_t i = 0; i < arraysize(values); ++i) {
59 float t = values[i];
60 Quaternion a(t, 2 * t, 3 * t, 4 * t);
61 Quaternion b(5 * t, 4 * t, 3 * t, 2 * t);
62 Quaternion sum = a + b;
63 CompareQuaternions(Quaternion(t, t, t, t) * 6, sum);
64 }
65 }
66
TEST(QuatTest,Multiplication)67 TEST(QuatTest, Multiplication) {
68 struct {
69 Quaternion a;
70 Quaternion b;
71 Quaternion expected;
72 } cases[] = {
73 {Quaternion(1, 0, 0, 0), Quaternion(1, 0, 0, 0), Quaternion(0, 0, 0, -1)},
74 {Quaternion(0, 1, 0, 0), Quaternion(0, 1, 0, 0), Quaternion(0, 0, 0, -1)},
75 {Quaternion(0, 0, 1, 0), Quaternion(0, 0, 1, 0), Quaternion(0, 0, 0, -1)},
76 {Quaternion(0, 0, 0, 1), Quaternion(0, 0, 0, 1), Quaternion(0, 0, 0, 1)},
77 {Quaternion(1, 2, 3, 4), Quaternion(5, 6, 7, 8),
78 Quaternion(24, 48, 48, -6)},
79 {Quaternion(5, 6, 7, 8), Quaternion(1, 2, 3, 4),
80 Quaternion(32, 32, 56, -6)},
81 };
82
83 for (size_t i = 0; i < arraysize(cases); ++i) {
84 Quaternion product = cases[i].a * cases[i].b;
85 CompareQuaternions(cases[i].expected, product);
86 }
87 }
88
TEST(QuatTest,Scaling)89 TEST(QuatTest, Scaling) {
90 double values[] = {0, 10, 100};
91 for (size_t i = 0; i < arraysize(values); ++i) {
92 double s = values[i];
93 Quaternion q(1, 2, 3, 4);
94 Quaternion expected(s, 2 * s, 3 * s, 4 * s);
95 CompareQuaternions(expected, q * s);
96 CompareQuaternions(expected, s * q);
97 if (s > 0)
98 CompareQuaternions(expected, q / (1 / s));
99 }
100 }
101
TEST(QuatTest,Normalization)102 TEST(QuatTest, Normalization) {
103 Quaternion q(1, -1, 1, -1);
104 EXPECT_NEAR(q.Length(), 4, kEpsilon);
105
106 q = q.Normalized();
107
108 EXPECT_NEAR(q.Length(), 1, kEpsilon);
109 EXPECT_NEAR(q.x(), 0.5, kEpsilon);
110 EXPECT_NEAR(q.y(), -0.5, kEpsilon);
111 EXPECT_NEAR(q.z(), 0.5, kEpsilon);
112 EXPECT_NEAR(q.w(), -0.5, kEpsilon);
113 }
114
TEST(QuatTest,Lerp)115 TEST(QuatTest, Lerp) {
116 for (size_t i = 1; i < 100; ++i) {
117 Quaternion a(0, 0, 0, 0);
118 Quaternion b(1, 2, 3, 4);
119 float t = static_cast<float>(i) / 100.0f;
120 Quaternion interpolated = a.Lerp(b, t);
121 double s = 1.0 / sqrt(30.0);
122 CompareQuaternions(Quaternion(1, 2, 3, 4) * s, interpolated);
123 }
124
125 Quaternion a(4, 3, 2, 1);
126 Quaternion b(1, 2, 3, 4);
127 CompareQuaternions(a.Normalized(), a.Lerp(b, 0));
128 CompareQuaternions(b.Normalized(), a.Lerp(b, 1));
129 CompareQuaternions(Quaternion(1, 1, 1, 1).Normalized(), a.Lerp(b, 0.5));
130 }
131
TEST(QuatTest,Slerp)132 TEST(QuatTest, Slerp) {
133 Vector3dF axis(1, 1, 1);
134 double start_radians = -0.5;
135 double stop_radians = 0.5;
136 Quaternion start(axis, start_radians);
137 Quaternion stop(axis, stop_radians);
138
139 for (size_t i = 0; i < 100; ++i) {
140 float t = static_cast<float>(i) / 100.0f;
141 double radians = (1.0 - t) * start_radians + t * stop_radians;
142 Quaternion expected(axis, radians);
143 Quaternion interpolated = start.Slerp(stop, t);
144 EXPECT_NEAR(expected.x(), interpolated.x(), kEpsilon);
145 EXPECT_NEAR(expected.y(), interpolated.y(), kEpsilon);
146 EXPECT_NEAR(expected.z(), interpolated.z(), kEpsilon);
147 EXPECT_NEAR(expected.w(), interpolated.w(), kEpsilon);
148 }
149 }
150
TEST(QuatTest,SlerpOppositeAngles)151 TEST(QuatTest, SlerpOppositeAngles) {
152 Vector3dF axis(1, 1, 1);
153 double start_radians = -M_PI_2;
154 double stop_radians = M_PI_2;
155 Quaternion start(axis, start_radians);
156 Quaternion stop(axis, stop_radians);
157
158 // When quaternions are pointed in the fully opposite direction, this is
159 // ambiguous, so we rotate as per https://www.w3.org/TR/css-transforms-1/
160 Quaternion expected(axis, 0);
161
162 Quaternion interpolated = start.Slerp(stop, 0.5f);
163 EXPECT_NEAR(expected.x(), interpolated.x(), kEpsilon);
164 EXPECT_NEAR(expected.y(), interpolated.y(), kEpsilon);
165 EXPECT_NEAR(expected.z(), interpolated.z(), kEpsilon);
166 EXPECT_NEAR(expected.w(), interpolated.w(), kEpsilon);
167 }
168
169 } // namespace gfx
170