Introduce QQuaternion::fromDirection()

...which constructs a quaternion using specified forward and upward
directions, so that the resulting Z axis "faces" a given forward direction.

Change-Id: Ib77b8ab5c359a4880b0d946face87026acdc6f0b
Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
Reviewed-by: Michael Krasnyk <michael.krasnyk@gmail.com>
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
This commit is contained in:
Konstantin Ritt 2015-02-26 18:04:43 +04:00
parent 3fb6014ce7
commit 95d034a14f
3 changed files with 126 additions and 0 deletions

View File

@ -717,11 +717,41 @@ QQuaternion QQuaternion::fromAxes(const QVector3D &xAxis, const QVector3D &yAxis
return QQuaternion::fromRotationMatrix(rot3x3);
}
/*!
\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)
{

View File

@ -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

View File

@ -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()
{