diff --git a/src/gui/math3d/qquaternion.cpp b/src/gui/math3d/qquaternion.cpp index 141651eda1..d367f74e25 100644 --- a/src/gui/math3d/qquaternion.cpp +++ b/src/gui/math3d/qquaternion.cpp @@ -592,7 +592,7 @@ QQuaternion QQuaternion::fromEulerAngles(float pitch, float yaw, float roll) \note If this quaternion is not normalized, the resulting rotation matrix will contain scaling information. - \sa fromRotationMatrix() + \sa fromRotationMatrix(), getAxes() */ QMatrix3x3 QQuaternion::toRotationMatrix() const { @@ -635,7 +635,7 @@ QMatrix3x3 QQuaternion::toRotationMatrix() const \note If a given rotation matrix is not normalized, the resulting quaternion will contain scaling information. - \sa toRotationMatrix() + \sa toRotationMatrix(), fromAxes() */ QQuaternion QQuaternion::fromRotationMatrix(const QMatrix3x3 &rot3x3) { @@ -672,6 +672,53 @@ QQuaternion QQuaternion::fromRotationMatrix(const QMatrix3x3 &rot3x3) return QQuaternion(scalar, axis[0], axis[1], axis[2]); } +#ifndef QT_NO_VECTOR3D + +/*! + \since 5.5 + + Returns the 3 orthonormal axes (\a xAxis, \a yAxis, \a zAxis) defining the quaternion. + + \sa fromAxes(), toRotationMatrix() +*/ +void QQuaternion::getAxes(QVector3D *xAxis, QVector3D *yAxis, QVector3D *zAxis) const +{ + Q_ASSERT(xAxis && yAxis && zAxis); + + const QMatrix3x3 rot3x3(toRotationMatrix()); + + *xAxis = QVector3D(rot3x3(0, 0), rot3x3(1, 0), rot3x3(2, 0)); + *yAxis = QVector3D(rot3x3(0, 1), rot3x3(1, 1), rot3x3(2, 1)); + *zAxis = QVector3D(rot3x3(0, 2), rot3x3(1, 2), rot3x3(2, 2)); +} + +/*! + \since 5.5 + + Constructs the quaternion using 3 axes (\a xAxis, \a yAxis, \a zAxis). + + \note The axes are assumed to be orthonormal. + + \sa getAxes(), fromRotationMatrix() +*/ +QQuaternion QQuaternion::fromAxes(const QVector3D &xAxis, const QVector3D &yAxis, const QVector3D &zAxis) +{ + QMatrix3x3 rot3x3(Qt::Uninitialized); + rot3x3(0, 0) = xAxis.x(); + rot3x3(1, 0) = xAxis.y(); + rot3x3(2, 0) = xAxis.z(); + rot3x3(0, 1) = yAxis.x(); + rot3x3(1, 1) = yAxis.y(); + rot3x3(2, 1) = yAxis.z(); + rot3x3(0, 2) = zAxis.x(); + rot3x3(1, 2) = zAxis.y(); + rot3x3(2, 2) = zAxis.z(); + + return QQuaternion::fromRotationMatrix(rot3x3); +} + +#endif // QT_NO_VECTOR3D + /*! \fn bool operator==(const QQuaternion &q1, const QQuaternion &q2) \relates QQuaternion diff --git a/src/gui/math3d/qquaternion.h b/src/gui/math3d/qquaternion.h index b4022e8579..6ce3979bbe 100644 --- a/src/gui/math3d/qquaternion.h +++ b/src/gui/math3d/qquaternion.h @@ -134,6 +134,11 @@ public: QMatrix3x3 toRotationMatrix() const; static QQuaternion fromRotationMatrix(const QMatrix3x3 &rot3x3); +#ifndef QT_NO_VECTOR3D + void getAxes(QVector3D *xAxis, QVector3D *yAxis, QVector3D *zAxis) const; + static QQuaternion fromAxes(const QVector3D &xAxis, const QVector3D &yAxis, const QVector3D &zAxis); +#endif + static QQuaternion slerp (const QQuaternion& q1, const QQuaternion& q2, float t); static QQuaternion nlerp diff --git a/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp b/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp index e358937a62..6b8eeeaca5 100644 --- a/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp +++ b/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp @@ -35,6 +35,52 @@ #include #include +// 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 @@ -89,6 +135,9 @@ private slots: void fromRotationMatrix_data(); void fromRotationMatrix(); + void fromAxes_data(); + void fromAxes(); + void fromEulerAngles_data(); void fromEulerAngles(); @@ -826,40 +875,58 @@ void tst_QQuaternion::fromRotationMatrix() QVERIFY(qFuzzyCompare(answer, result) || qFuzzyCompare(-answer, result)); } -// 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) +// Test quaternion convertion to and from orthonormal axes. +void tst_QQuaternion::fromAxes_data() { - if (qFuzzyIsNull(p1)) - return qFuzzyIsNull(p2); - if (qFuzzyIsNull(p2)) - return false; - // a very slightly looser version of qFuzzyCompare - // for use with values that are not very close to zero - return qAbs(p1 - p2) <= 0.00003f * qMin(qAbs(p1), qAbs(p2)); + QTest::addColumn("x1"); + QTest::addColumn("y1"); + QTest::addColumn("z1"); + QTest::addColumn("angle"); + QTest::addColumn("xAxis"); + QTest::addColumn("yAxis"); + QTest::addColumn("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); } - -static inline bool myFuzzyCompareRadians(float p1, float p2) +void tst_QQuaternion::fromAxes() { - static const float fPI = float(M_PI); - if (p1 < -fPI) - p1 += 2.0f * fPI; - else if (p1 > fPI) - p1 -= 2.0f * fPI; + QFETCH(float, x1); + QFETCH(float, y1); + QFETCH(float, z1); + QFETCH(float, angle); + QFETCH(QVector3D, xAxis); + QFETCH(QVector3D, yAxis); + QFETCH(QVector3D, zAxis); - if (p2 < -fPI) - p2 += 2.0f * fPI; - else if (p2 > fPI) - p2 -= 2.0f * fPI; + QQuaternion result = QQuaternion::fromAxisAndAngle(QVector3D(x1, y1, z1), angle); - return qAbs(qAbs(p1) - qAbs(p2)) <= qDegreesToRadians(0.05f); -} + 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)); -static inline bool myFuzzyCompareDegrees(float p1, float p2) -{ - p1 = qDegreesToRadians(p1); - p2 = qDegreesToRadians(p2); - return myFuzzyCompareRadians(p1, p2); + QQuaternion answer = QQuaternion::fromAxes(axes[0], axes[1], axes[2]); + + QVERIFY(qFuzzyCompare(answer, result) || qFuzzyCompare(-answer, result)); } // Test quaternion creation from an axis and an angle.