[QQuaternion] Add a way to convert to/from orthonormal axes

It is just a convenience wrapper
around convertion to/from the rotation matrix.

Change-Id: I27511b43866827172960b0152f1c7b65da857f6f
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
This commit is contained in:
Konstantin Ritt 2015-02-26 19:16:15 +04:00
parent c4aea1ea20
commit 1e441d298d
3 changed files with 149 additions and 30 deletions

View File

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

View File

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

View File

@ -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
@ -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<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);
}
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.