diff options
-rw-r--r-- | src/gui/math3d/qquaternion.cpp | 30 | ||||
-rw-r--r-- | src/gui/math3d/qquaternion.h | 2 | ||||
-rw-r--r-- | tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp | 94 |
3 files changed, 126 insertions, 0 deletions
diff --git a/src/gui/math3d/qquaternion.cpp b/src/gui/math3d/qquaternion.cpp index 6914a0b45e..4b35ee4e79 100644 --- a/src/gui/math3d/qquaternion.cpp +++ b/src/gui/math3d/qquaternion.cpp @@ -720,8 +720,38 @@ QQuaternion QQuaternion::fromAxes(const QVector3D &xAxis, const QVector3D &yAxis /*! \since 5.5 + Constructs the quaternion using specified forward direction \a direction + and upward direction \a up. + If the upward direction was not specified or the forward and upward + vectors are collinear, a new orthonormal upward direction will be generated. + + \sa fromAxes(), rotationTo() +*/ +QQuaternion QQuaternion::fromDirection(const QVector3D &direction, const QVector3D &up) +{ + if (qFuzzyIsNull(direction.x()) && qFuzzyIsNull(direction.y()) && qFuzzyIsNull(direction.z())) + return QQuaternion(); + + const QVector3D zAxis(direction.normalized()); + QVector3D xAxis(QVector3D::crossProduct(up, zAxis)); + if (qFuzzyIsNull(xAxis.lengthSquared())) { + // collinear or invalid up vector; derive shortest arc to new direction + return QQuaternion::rotationTo(QVector3D(0.0f, 0.0f, 1.0f), zAxis); + } + + xAxis.normalize(); + const QVector3D yAxis(QVector3D::crossProduct(zAxis, xAxis)); + + return QQuaternion::fromAxes(xAxis, yAxis, zAxis); +} + +/*! + \since 5.5 + Returns the shortest arc quaternion to rotate from the direction described by the vector \a from to the direction described by the vector \a to. + + \sa fromDirection() */ QQuaternion QQuaternion::rotationTo(const QVector3D &from, const QVector3D &to) { diff --git a/src/gui/math3d/qquaternion.h b/src/gui/math3d/qquaternion.h index c3918645d4..240e31a5c2 100644 --- a/src/gui/math3d/qquaternion.h +++ b/src/gui/math3d/qquaternion.h @@ -138,6 +138,8 @@ public: void getAxes(QVector3D *xAxis, QVector3D *yAxis, QVector3D *zAxis) const; static QQuaternion fromAxes(const QVector3D &xAxis, const QVector3D &yAxis, const QVector3D &zAxis); + static QQuaternion fromDirection(const QVector3D &direction, const QVector3D &up); + static QQuaternion rotationTo(const QVector3D &from, const QVector3D &to); #endif diff --git a/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp b/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp index 2c38e4c111..ec7af97f07 100644 --- a/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp +++ b/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp @@ -141,6 +141,9 @@ private slots: void rotationTo_data(); void rotationTo(); + void fromDirection_data(); + void fromDirection(); + void fromEulerAngles_data(); void fromEulerAngles(); @@ -989,6 +992,97 @@ void tst_QQuaternion::rotationTo() QVERIFY(myFuzzyCompare(vec2, from)); } +static QByteArray testnameForAxis(const QVector3D &axis) +{ + QByteArray testname; + if (axis == QVector3D()) { + testname = "null"; + } else { + if (axis.x()) { + testname += axis.x() < 0 ? "-" : "+"; + testname += "X"; + } + if (axis.y()) { + testname += axis.y() < 0 ? "-" : "+"; + testname += "Y"; + } + if (axis.z()) { + testname += axis.z() < 0 ? "-" : "+"; + testname += "Z"; + } + } + return testname; +} + +// Test quaternion convertion to and from orthonormal axes. +void tst_QQuaternion::fromDirection_data() +{ + QTest::addColumn<QVector3D>("direction"); + QTest::addColumn<QVector3D>("up"); + + QList<QQuaternion> orientations; + orientations << QQuaternion(); + for (int angle = 45; angle <= 360; angle += 45) { + orientations << QQuaternion::fromAxisAndAngle(QVector3D(1, 0, 0), angle) + << QQuaternion::fromAxisAndAngle(QVector3D(0, 1, 0), angle) + << QQuaternion::fromAxisAndAngle(QVector3D(0, 0, 1), angle) + << QQuaternion::fromAxisAndAngle(QVector3D(1, 0, 0), angle) + * QQuaternion::fromAxisAndAngle(QVector3D(0, 1, 0), angle) + * QQuaternion::fromAxisAndAngle(QVector3D(0, 0, 1), angle); + } + + // othonormal up and dir + foreach (const QQuaternion &q, orientations) { + QVector3D xAxis, yAxis, zAxis; + q.getAxes(&xAxis, &yAxis, &zAxis); + + QTest::newRow("dir: " + testnameForAxis(zAxis) + ", up: " + testnameForAxis(yAxis)) + << zAxis * 10.0f << yAxis * 10.0f; + } + + // collinear up and dir + QTest::newRow("dir: +X, up: +X") << QVector3D(10.0f, 0.0f, 0.0f) << QVector3D(10.0f, 0.0f, 0.0f); + QTest::newRow("dir: +X, up: -X") << QVector3D(10.0f, 0.0f, 0.0f) << QVector3D(-10.0f, 0.0f, 0.0f); + QTest::newRow("dir: +Y, up: +Y") << QVector3D(0.0f, 10.0f, 0.0f) << QVector3D(0.0f, 10.0f, 0.0f); + QTest::newRow("dir: +Y, up: -Y") << QVector3D(0.0f, 10.0f, 0.0f) << QVector3D(0.0f, -10.0f, 0.0f); + QTest::newRow("dir: +Z, up: +Z") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(0.0f, 0.0f, 10.0f); + QTest::newRow("dir: +Z, up: -Z") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(0.0f, 0.0f, -10.0f); + QTest::newRow("dir: +X+Y+Z, up: +X+Y+Z") << QVector3D(10.0f, 10.0f, 10.0f) << QVector3D(10.0f, 10.0f, 10.0f); + QTest::newRow("dir: +X+Y+Z, up: -X-Y-Z") << QVector3D(10.0f, 10.0f, 10.0f) << QVector3D(-10.0f, -10.0f, -10.0f); + + // invalid up + foreach (const QQuaternion &q, orientations) { + QVector3D xAxis, yAxis, zAxis; + q.getAxes(&xAxis, &yAxis, &zAxis); + + QTest::newRow("dir: " + testnameForAxis(zAxis) + ", up: null") + << zAxis * 10.0f << QVector3D(); + } +} +void tst_QQuaternion::fromDirection() +{ + QFETCH(QVector3D, direction); + QFETCH(QVector3D, up); + + QVector3D expextedZ(direction != QVector3D() ? direction.normalized() : QVector3D(0, 0, 1)); + QVector3D expextedY(up.normalized()); + + QQuaternion result = QQuaternion::fromDirection(direction, up); + QVERIFY(myFuzzyCompare(result, result.normalized())); + + QVector3D xAxis, yAxis, zAxis; + result.getAxes(&xAxis, &yAxis, &zAxis); + + QVERIFY(myFuzzyCompare(zAxis, expextedZ)); + + if (!qFuzzyIsNull(QVector3D::crossProduct(expextedZ, expextedY).lengthSquared())) { + QVector3D expextedX(QVector3D::crossProduct(expextedY, expextedZ)); + + QVERIFY(myFuzzyCompare(yAxis, expextedY)); + QVERIFY(myFuzzyCompare(xAxis, expextedX)); + } +} + // Test quaternion creation from an axis and an angle. void tst_QQuaternion::fromEulerAngles_data() { |