summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/gui/math3d/qquaternion.cpp30
-rw-r--r--src/gui/math3d/qquaternion.h2
-rw-r--r--tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp94
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()
{