diff options
Diffstat (limited to 'tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp')
-rw-r--r-- | tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp | 386 |
1 files changed, 376 insertions, 10 deletions
diff --git a/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp b/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp index cdbc242640..2c38e4c111 100644 --- a/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp +++ b/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** @@ -10,9 +10,9 @@ ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser @@ -23,8 +23,8 @@ ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ @@ -35,6 +35,52 @@ #include <QtCore/qmath.h> #include <QtGui/qquaternion.h> +// This is a more tolerant version of qFuzzyCompare that also handles the case +// where one or more of the values being compare are close to zero +static inline bool myFuzzyCompare(float p1, float p2) +{ + if (qFuzzyIsNull(p1) && qFuzzyIsNull(p2)) + return true; + return qAbs(qAbs(p1) - qAbs(p2)) <= 0.00003f; +} + +static inline bool myFuzzyCompare(const QVector3D &v1, const QVector3D &v2) +{ + return myFuzzyCompare(v1.x(), v2.x()) + && myFuzzyCompare(v1.y(), v2.y()) + && myFuzzyCompare(v1.z(), v2.z()); +} + +static inline bool myFuzzyCompare(const QQuaternion &q1, const QQuaternion &q2) +{ + const float d = QQuaternion::dotProduct(q1, q2); + return myFuzzyCompare(d * d, 1.0f); +} + +static inline bool myFuzzyCompareRadians(float p1, float p2) +{ + static const float fPI = float(M_PI); + if (p1 < -fPI) + p1 += 2.0f * fPI; + else if (p1 > fPI) + p1 -= 2.0f * fPI; + + if (p2 < -fPI) + p2 += 2.0f * fPI; + else if (p2 > fPI) + p2 -= 2.0f * fPI; + + return qAbs(qAbs(p1) - qAbs(p2)) <= qDegreesToRadians(0.05f); +} + +static inline bool myFuzzyCompareDegrees(float p1, float p2) +{ + p1 = qDegreesToRadians(p1); + p2 = qDegreesToRadians(p2); + return myFuzzyCompareRadians(p1, p2); +} + + class tst_QQuaternion : public QObject { Q_OBJECT @@ -45,6 +91,9 @@ public: private slots: void create(); + void dotProduct_data(); + void dotProduct(); + void length_data(); void length(); @@ -54,6 +103,9 @@ private slots: void normalize_data(); void normalize(); + void inverted_data(); + void inverted(); + void compare(); void add_data(); @@ -80,6 +132,18 @@ private slots: void fromAxisAndAngle_data(); void fromAxisAndAngle(); + void fromRotationMatrix_data(); + void fromRotationMatrix(); + + void fromAxes_data(); + void fromAxes(); + + void rotationTo_data(); + void rotationTo(); + + void fromEulerAngles_data(); + void fromEulerAngles(); + void slerp_data(); void slerp(); @@ -210,6 +274,58 @@ void tst_QQuaternion::create() QCOMPARE(v10.w(), 34.0f); } +// Test the computation of dot product. +void tst_QQuaternion::dotProduct_data() +{ + QTest::addColumn<float>("x1"); + QTest::addColumn<float>("y1"); + QTest::addColumn<float>("z1"); + QTest::addColumn<float>("scalar1"); + QTest::addColumn<float>("x2"); + QTest::addColumn<float>("y2"); + QTest::addColumn<float>("z2"); + QTest::addColumn<float>("scalar2"); + QTest::addColumn<float>("dot"); + + QTest::newRow("null") + << 0.0f << 0.0f << 0.0f << 0.0f + << 0.0f << 0.0f << 0.0f << 0.0f + << 0.0f; + + QTest::newRow("identity") + << 0.0f << 0.0f << 0.0f << 1.0f + << 0.0f << 0.0f << 0.0f << 1.0f + << 1.0f; + + QTest::newRow("unitvec") + << 1.0f << 0.0f << 0.0f << 0.0f + << 0.0f << 1.0f << 0.0f << 0.0f + << 0.0f; + + QTest::newRow("complex") + << 1.0f << 2.0f << 3.0f << 4.0f + << 4.0f << 5.0f << 6.0f << 7.0f + << 60.0f; +} +void tst_QQuaternion::dotProduct() +{ + QFETCH(float, x1); + QFETCH(float, y1); + QFETCH(float, z1); + QFETCH(float, scalar1); + QFETCH(float, x2); + QFETCH(float, y2); + QFETCH(float, z2); + QFETCH(float, scalar2); + QFETCH(float, dot); + + QQuaternion q1(scalar1, x1, y1, z1); + QQuaternion q2(scalar2, x2, y2, z2); + + QCOMPARE(QQuaternion::dotProduct(q1, q2), dot); + QCOMPARE(QQuaternion::dotProduct(q2, q1), dot); +} + // Test length computation for quaternions. void tst_QQuaternion::length_data() { @@ -228,7 +344,7 @@ void tst_QQuaternion::length_data() QTest::newRow("-1y") << 0.0f << -1.0f << 0.0f << 0.0f << 1.0f; QTest::newRow("-1z") << 0.0f << 0.0f << -1.0f << 0.0f << 1.0f; QTest::newRow("-1w") << 0.0f << 0.0f << 0.0f << -1.0f << 1.0f; - QTest::newRow("two") << 2.0f << -2.0f << 2.0f << 2.0f << sqrtf(16.0f); + QTest::newRow("two") << 2.0f << -2.0f << 2.0f << 2.0f << std::sqrt(16.0f); } void tst_QQuaternion::length() { @@ -291,6 +407,32 @@ void tst_QQuaternion::normalize() QCOMPARE(v.length(), 1.0f); } +void tst_QQuaternion::inverted_data() +{ + // Use the same test data as the length test. + length_data(); +} +void tst_QQuaternion::inverted() +{ + QFETCH(float, x); + QFETCH(float, y); + QFETCH(float, z); + QFETCH(float, w); + QFETCH(float, len); + + QQuaternion v(w, x, y, z); + QQuaternion u = v.inverted(); + if (v.isNull()) { + QVERIFY(u.isNull()); + } else { + len *= len; + QCOMPARE(-u.x() * len, v.x()); + QCOMPARE(-u.y() * len, v.y()); + QCOMPARE(-u.z() * len, v.z()); + QCOMPARE(u.scalar() * len, v.scalar()); + } +} + // Test the comparison operators for quaternions. void tst_QQuaternion::compare() { @@ -675,8 +817,9 @@ void tst_QQuaternion::fromAxisAndAngle() // http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q56 // to calculate the answer we expect to get. QVector3D vector = QVector3D(x1, y1, z1).normalized(); - float sin_a = sinf((angle * M_PI / 180.0) / 2.0); - float cos_a = cosf((angle * M_PI / 180.0) / 2.0); + const float a = (angle * M_PI / 180.0) / 2.0; + const float sin_a = std::sin(a); + const float cos_a = std::cos(a); QQuaternion result(cos_a, (vector.x() * sin_a), (vector.y() * sin_a), @@ -689,11 +832,234 @@ void tst_QQuaternion::fromAxisAndAngle() QVERIFY(qFuzzyCompare(answer.z(), result.z())); QVERIFY(qFuzzyCompare(answer.scalar(), result.scalar())); + { + QVector3D answerAxis; + float answerAngle; + answer.getAxisAndAngle(&answerAxis, &answerAngle); + QVERIFY(qFuzzyCompare(answerAxis.x(), vector.x())); + QVERIFY(qFuzzyCompare(answerAxis.y(), vector.y())); + QVERIFY(qFuzzyCompare(answerAxis.z(), vector.z())); + QVERIFY(qFuzzyCompare(answerAngle, angle)); + } + answer = QQuaternion::fromAxisAndAngle(x1, y1, z1, angle); QVERIFY(qFuzzyCompare(answer.x(), result.x())); QVERIFY(qFuzzyCompare(answer.y(), result.y())); QVERIFY(qFuzzyCompare(answer.z(), result.z())); QVERIFY(qFuzzyCompare(answer.scalar(), result.scalar())); + + { + float answerAxisX, answerAxisY, answerAxisZ; + float answerAngle; + answer.getAxisAndAngle(&answerAxisX, &answerAxisY, &answerAxisZ, &answerAngle); + QVERIFY(qFuzzyCompare(answerAxisX, vector.x())); + QVERIFY(qFuzzyCompare(answerAxisY, vector.y())); + QVERIFY(qFuzzyCompare(answerAxisZ, vector.z())); + QVERIFY(qFuzzyCompare(answerAngle, angle)); + } +} + +// Test quaternion convertion to and from rotation matrix. +void tst_QQuaternion::fromRotationMatrix_data() +{ + fromAxisAndAngle_data(); +} +void tst_QQuaternion::fromRotationMatrix() +{ + QFETCH(float, x1); + QFETCH(float, y1); + QFETCH(float, z1); + QFETCH(float, angle); + + QQuaternion result = QQuaternion::fromAxisAndAngle(QVector3D(x1, y1, z1), angle); + QMatrix3x3 rot3x3 = result.toRotationMatrix(); + QQuaternion answer = QQuaternion::fromRotationMatrix(rot3x3); + + QVERIFY(qFuzzyCompare(answer, result) || qFuzzyCompare(-answer, result)); +} + +// Test quaternion convertion to and from orthonormal axes. +void tst_QQuaternion::fromAxes_data() +{ + QTest::addColumn<float>("x1"); + QTest::addColumn<float>("y1"); + QTest::addColumn<float>("z1"); + QTest::addColumn<float>("angle"); + QTest::addColumn<QVector3D>("xAxis"); + QTest::addColumn<QVector3D>("yAxis"); + QTest::addColumn<QVector3D>("zAxis"); + + QTest::newRow("null") + << 0.0f << 0.0f << 0.0f << 0.0f + << QVector3D(1, 0, 0) << QVector3D(0, 1, 0) << QVector3D(0, 0, 1); + + QTest::newRow("xonly") + << 1.0f << 0.0f << 0.0f << 90.0f + << QVector3D(1, 0, 0) << QVector3D(0, 0, 1) << QVector3D(0, -1, 0); + + QTest::newRow("yonly") + << 0.0f << 1.0f << 0.0f << 180.0f + << QVector3D(-1, 0, 0) << QVector3D(0, 1, 0) << QVector3D(0, 0, -1); + + QTest::newRow("zonly") + << 0.0f << 0.0f << 1.0f << 270.0f + << QVector3D(0, -1, 0) << QVector3D(1, 0, 0) << QVector3D(0, 0, 1); + + QTest::newRow("complex") + << 1.0f << 2.0f << -3.0f << 45.0f + << QVector3D(0.728028, -0.525105, -0.440727) << QVector3D(0.608789, 0.790791, 0.0634566) << QVector3D(0.315202, -0.314508, 0.895395); +} +void tst_QQuaternion::fromAxes() +{ + QFETCH(float, x1); + QFETCH(float, y1); + QFETCH(float, z1); + QFETCH(float, angle); + QFETCH(QVector3D, xAxis); + QFETCH(QVector3D, yAxis); + QFETCH(QVector3D, zAxis); + + QQuaternion result = QQuaternion::fromAxisAndAngle(QVector3D(x1, y1, z1), angle); + + QVector3D axes[3]; + result.getAxes(&axes[0], &axes[1], &axes[2]); + QVERIFY(myFuzzyCompare(axes[0], xAxis)); + QVERIFY(myFuzzyCompare(axes[1], yAxis)); + QVERIFY(myFuzzyCompare(axes[2], zAxis)); + + QQuaternion answer = QQuaternion::fromAxes(axes[0], axes[1], axes[2]); + + QVERIFY(qFuzzyCompare(answer, result) || qFuzzyCompare(-answer, result)); +} + +// Test shortest arc quaternion. +void tst_QQuaternion::rotationTo_data() +{ + QTest::addColumn<QVector3D>("from"); + QTest::addColumn<QVector3D>("to"); + + // same + QTest::newRow("+X -> +X") << QVector3D(10.0f, 0.0f, 0.0f) << QVector3D(10.0f, 0.0f, 0.0f); + QTest::newRow("-X -> -X") << QVector3D(-10.0f, 0.0f, 0.0f) << QVector3D(-10.0f, 0.0f, 0.0f); + QTest::newRow("+Y -> +Y") << QVector3D(0.0f, 10.0f, 0.0f) << QVector3D(0.0f, 10.0f, 0.0f); + QTest::newRow("-Y -> -Y") << QVector3D(0.0f, -10.0f, 0.0f) << QVector3D(0.0f, -10.0f, 0.0f); + QTest::newRow("+Z -> +Z") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(0.0f, 0.0f, 10.0f); + QTest::newRow("-Z -> -Z") << QVector3D(0.0f, 0.0f, -10.0f) << QVector3D(0.0f, 0.0f, -10.0f); + QTest::newRow("+X+Y+Z -> +X+Y+Z") << QVector3D(10.0f, 10.0f, 10.0f) << QVector3D(10.0f, 10.0f, 10.0f); + QTest::newRow("-X-Y-Z -> -X-Y-Z") << QVector3D(-10.0f, -10.0f, -10.0f) << QVector3D(-10.0f, -10.0f, -10.0f); + + // arbitrary + QTest::newRow("+Z -> +X") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(10.0f, 0.0f, 0.0f); + QTest::newRow("+Z -> -X") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(-10.0f, 0.0f, 0.0f); + QTest::newRow("+Z -> +Y") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(0.0f, 10.0f, 0.0f); + QTest::newRow("+Z -> -Y") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(0.0f, -10.0f, 0.0f); + QTest::newRow("-Z -> +X") << QVector3D(0.0f, 0.0f, -10.0f) << QVector3D(10.0f, 0.0f, 0.0f); + QTest::newRow("-Z -> -X") << QVector3D(0.0f, 0.0f, -10.0f) << QVector3D(-10.0f, 0.0f, 0.0f); + QTest::newRow("-Z -> +Y") << QVector3D(0.0f, 0.0f, -10.0f) << QVector3D(0.0f, 10.0f, 0.0f); + QTest::newRow("-Z -> -Y") << QVector3D(0.0f, 0.0f, -10.0f) << QVector3D(0.0f, -10.0f, 0.0f); + QTest::newRow("+X -> +Y") << QVector3D(10.0f, 0.0f, 0.0f) << QVector3D(0.0f, 10.0f, 0.0f); + QTest::newRow("+X -> -Y") << QVector3D(10.0f, 0.0f, 0.0f) << QVector3D(0.0f, -10.0f, 0.0f); + QTest::newRow("-X -> +Y") << QVector3D(-10.0f, 0.0f, 0.0f) << QVector3D(0.0f, 10.0f, 0.0f); + QTest::newRow("-X -> -Y") << QVector3D(-10.0f, 0.0f, 0.0f) << QVector3D(0.0f, -10.0f, 0.0f); + QTest::newRow("+X+Y+Z -> +X-Y-Z") << QVector3D(10.0f, 10.0f, 10.0f) << QVector3D(10.0f, -10.0f, -10.0f); + QTest::newRow("-X-Y+Z -> -X+Y-Z") << QVector3D(-10.0f, -10.0f, 10.0f) << QVector3D(-10.0f, 10.0f, -10.0f); + QTest::newRow("+X+Y+Z -> +Z") << QVector3D(10.0f, 10.0f, 10.0f) << QVector3D(0.0f, 0.0f, 10.0f); + + // collinear + QTest::newRow("+X -> -X") << QVector3D(10.0f, 0.0f, 0.0f) << QVector3D(-10.0f, 0.0f, 0.0f); + QTest::newRow("+Y -> -Y") << QVector3D(0.0f, 10.0f, 0.0f) << QVector3D(0.0f, -10.0f, 0.0f); + QTest::newRow("+Z -> -Z") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(0.0f, 0.0f, -10.0f); + QTest::newRow("+X+Y+Z -> -X-Y-Z") << QVector3D(10.0f, 10.0f, 10.0f) << QVector3D(-10.0f, -10.0f, -10.0f); +} +void tst_QQuaternion::rotationTo() +{ + QFETCH(QVector3D, from); + QFETCH(QVector3D, to); + + QQuaternion q1 = QQuaternion::rotationTo(from, to); + QVERIFY(myFuzzyCompare(q1, q1.normalized())); + QVector3D vec1(q1 * from); + vec1 *= (to.length() / from.length()); // discard rotated length + QVERIFY(myFuzzyCompare(vec1, to)); + + QQuaternion q2 = QQuaternion::rotationTo(to, from); + QVERIFY(myFuzzyCompare(q2, q2.normalized())); + QVector3D vec2(q2 * to); + vec2 *= (from.length() / to.length()); // discard rotated length + QVERIFY(myFuzzyCompare(vec2, from)); +} + +// Test quaternion creation from an axis and an angle. +void tst_QQuaternion::fromEulerAngles_data() +{ + QTest::addColumn<float>("pitch"); + QTest::addColumn<float>("yaw"); + QTest::addColumn<float>("roll"); + + QTest::newRow("null") + << 0.0f << 0.0f << 0.0f; + + QTest::newRow("xonly") + << 90.0f << 0.0f << 0.0f; + + QTest::newRow("yonly") + << 0.0f << 180.0f << 0.0f; + + QTest::newRow("zonly") + << 0.0f << 0.0f << 270.0f; + + QTest::newRow("x+z") + << 30.0f << 0.0f << 45.0f; + + QTest::newRow("x+y") + << 30.0f << 90.0f << 0.0f; + + QTest::newRow("y+z") + << 0.0f << 45.0f << 30.0f; + + QTest::newRow("complex") + << 30.0f << 240.0f << -45.0f; +} +void tst_QQuaternion::fromEulerAngles() +{ + QFETCH(float, pitch); + QFETCH(float, yaw); + QFETCH(float, roll); + + // Use a straight-forward implementation of the algorithm at: + // http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q60 + // to calculate the answer we expect to get. + QQuaternion qx = QQuaternion::fromAxisAndAngle(QVector3D(1, 0, 0), pitch); + QQuaternion qy = QQuaternion::fromAxisAndAngle(QVector3D(0, 1, 0), yaw); + QQuaternion qz = QQuaternion::fromAxisAndAngle(QVector3D(0, 0, 1), roll); + QQuaternion result = qy * (qx * qz); + QQuaternion answer = QQuaternion::fromEulerAngles(QVector3D(pitch, yaw, roll)); + + QVERIFY(myFuzzyCompare(answer.x(), result.x())); + QVERIFY(myFuzzyCompare(answer.y(), result.y())); + QVERIFY(myFuzzyCompare(answer.z(), result.z())); + QVERIFY(myFuzzyCompare(answer.scalar(), result.scalar())); + + { + QVector3D answerEulerAngles = answer.toEulerAngles(); + QVERIFY(myFuzzyCompareDegrees(answerEulerAngles.x(), pitch)); + QVERIFY(myFuzzyCompareDegrees(answerEulerAngles.y(), yaw)); + QVERIFY(myFuzzyCompareDegrees(answerEulerAngles.z(), roll)); + } + + answer = QQuaternion::fromEulerAngles(pitch, yaw, roll); + QVERIFY(myFuzzyCompare(answer.x(), result.x())); + QVERIFY(myFuzzyCompare(answer.y(), result.y())); + QVERIFY(myFuzzyCompare(answer.z(), result.z())); + QVERIFY(myFuzzyCompare(answer.scalar(), result.scalar())); + + { + float answerPitch, answerYaw, answerRoll; + answer.getEulerAngles(&answerPitch, &answerYaw, &answerRoll); + QVERIFY(myFuzzyCompareDegrees(answerPitch, pitch)); + QVERIFY(myFuzzyCompareDegrees(answerYaw, yaw)); + QVERIFY(myFuzzyCompareDegrees(answerRoll, roll)); + } } // Test spherical interpolation of quaternions. |