summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRhys Weatherley <rhys.weatherley@nokia.com>2009-10-26 10:36:45 +1000
committerRhys Weatherley <rhys.weatherley@nokia.com>2009-10-26 11:03:23 +1000
commit1a299a9631a2d1b4e07cedfece7af5318a766fe6 (patch)
treebde51d4c5acdc7cd975322932bdbea02bcda59bb
parent0d6104d763f4cb32ac6117b3f951afcb73fc50e6 (diff)
downloadqt4-tools-1a299a9631a2d1b4e07cedfece7af5318a766fe6.tar.gz
Optimize QGraphicsRotation's use of QMatrix4x4
Previous code was creating a full 3D rotation matrix and then projecting back to 2D. This change combines the two steps into one to avoid calculating matrix components that will be dropped. Reviewed-by: Sarah Smith
-rw-r--r--src/gui/graphicsview/qgraphicstransform.cpp4
-rw-r--r--src/gui/math3d/qmatrix4x4.cpp108
-rw-r--r--src/gui/math3d/qmatrix4x4.h4
-rw-r--r--tests/auto/qgraphicstransform/tst_qgraphicstransform.cpp69
4 files changed, 179 insertions, 6 deletions
diff --git a/src/gui/graphicsview/qgraphicstransform.cpp b/src/gui/graphicsview/qgraphicstransform.cpp
index a0b5493a8c..93dc1961e0 100644
--- a/src/gui/graphicsview/qgraphicstransform.cpp
+++ b/src/gui/graphicsview/qgraphicstransform.cpp
@@ -547,9 +547,7 @@ void QGraphicsRotation::applyTo(QMatrix4x4 *matrix) const
return;
matrix->translate(d->origin);
- QMatrix4x4 m;
- m.rotate(d->angle, d->axis.x(), d->axis.y(), d->axis.z());
- *matrix *= m.toTransform(1024.0f); // Project back to 2D.
+ matrix->projectedRotate(d->angle, d->axis.x(), d->axis.y(), d->axis.z());
matrix->translate(-d->origin);
}
diff --git a/src/gui/math3d/qmatrix4x4.cpp b/src/gui/math3d/qmatrix4x4.cpp
index 00e8f1569e..fa3826fd61 100644
--- a/src/gui/math3d/qmatrix4x4.cpp
+++ b/src/gui/math3d/qmatrix4x4.cpp
@@ -58,6 +58,8 @@ QT_BEGIN_NAMESPACE
\sa QVector3D, QGenericMatrix
*/
+static const qreal inv_dist_to_plane = 1. / 1024.;
+
/*!
\fn QMatrix4x4::QMatrix4x4()
@@ -1103,6 +1105,110 @@ QMatrix4x4& QMatrix4x4::rotate(qreal angle, qreal x, qreal y, qreal z)
return *this;
}
+/*!
+ \internal
+*/
+QMatrix4x4& QMatrix4x4::projectedRotate(qreal angle, qreal x, qreal y, qreal z)
+{
+ // Used by QGraphicsRotation::applyTo() to perform a rotation
+ // and projection back to 2D in a single step.
+ if (angle == 0.0f)
+ return *this;
+ QMatrix4x4 m(1); // The "1" says to not load the identity.
+ qreal c, s, ic;
+ if (angle == 90.0f || angle == -270.0f) {
+ s = 1.0f;
+ c = 0.0f;
+ } else if (angle == -90.0f || angle == 270.0f) {
+ s = -1.0f;
+ c = 0.0f;
+ } else if (angle == 180.0f || angle == -180.0f) {
+ s = 0.0f;
+ c = -1.0f;
+ } else {
+ qreal a = angle * M_PI / 180.0f;
+ c = qCos(a);
+ s = qSin(a);
+ }
+ bool quick = false;
+ if (x == 0.0f) {
+ if (y == 0.0f) {
+ if (z != 0.0f) {
+ // Rotate around the Z axis.
+ m.setIdentity();
+ m.m[0][0] = c;
+ m.m[1][1] = c;
+ if (z < 0.0f) {
+ m.m[1][0] = s;
+ m.m[0][1] = -s;
+ } else {
+ m.m[1][0] = -s;
+ m.m[0][1] = s;
+ }
+ m.flagBits = General;
+ quick = true;
+ }
+ } else if (z == 0.0f) {
+ // Rotate around the Y axis.
+ m.setIdentity();
+ m.m[0][0] = c;
+ m.m[2][2] = 1.0f;
+ if (y < 0.0f) {
+ m.m[0][3] = -s * inv_dist_to_plane;
+ } else {
+ m.m[0][3] = s * inv_dist_to_plane;
+ }
+ m.flagBits = General;
+ quick = true;
+ }
+ } else if (y == 0.0f && z == 0.0f) {
+ // Rotate around the X axis.
+ m.setIdentity();
+ m.m[1][1] = c;
+ m.m[2][2] = 1.0f;
+ if (x < 0.0f) {
+ m.m[1][3] = s * inv_dist_to_plane;
+ } else {
+ m.m[1][3] = -s * inv_dist_to_plane;
+ }
+ m.flagBits = General;
+ quick = true;
+ }
+ if (!quick) {
+ qreal len = x * x + y * y + z * z;
+ if (!qFuzzyIsNull(len - 1.0f) && !qFuzzyIsNull(len)) {
+ len = qSqrt(len);
+ x /= len;
+ y /= len;
+ z /= len;
+ }
+ ic = 1.0f - c;
+ m.m[0][0] = x * x * ic + c;
+ m.m[1][0] = x * y * ic - z * s;
+ m.m[2][0] = 0.0f;
+ m.m[3][0] = 0.0f;
+ m.m[0][1] = y * x * ic + z * s;
+ m.m[1][1] = y * y * ic + c;
+ m.m[2][1] = 0.0f;
+ m.m[3][1] = 0.0f;
+ m.m[0][2] = 0.0f;
+ m.m[1][2] = 0.0f;
+ m.m[2][2] = 1.0f;
+ m.m[3][2] = 0.0f;
+ m.m[0][3] = (x * z * ic - y * s) * -inv_dist_to_plane;
+ m.m[1][3] = (y * z * ic + x * s) * -inv_dist_to_plane;
+ m.m[2][3] = 0.0f;
+ m.m[3][3] = 1.0f;
+ }
+ int flags = flagBits;
+ *this *= m;
+ if (flags != Identity)
+ flagBits = flags | Rotation;
+ else
+ flagBits = Rotation;
+ return *this;
+}
+
#ifndef QT_NO_VECTOR4D
/*!
@@ -1448,8 +1554,6 @@ QTransform QMatrix4x4::toTransform() const
m[3][0], m[3][1], m[3][3]);
}
-static const qreal inv_dist_to_plane = 1. / 1024.;
-
/*!
Returns the conventional Qt 2D transformation matrix that
corresponds to this matrix.
diff --git a/src/gui/math3d/qmatrix4x4.h b/src/gui/math3d/qmatrix4x4.h
index 42d992e3b3..ba74b897cb 100644
--- a/src/gui/math3d/qmatrix4x4.h
+++ b/src/gui/math3d/qmatrix4x4.h
@@ -207,6 +207,10 @@ private:
QMatrix4x4(int) { flagBits = General; }
QMatrix4x4 orthonormalInverse() const;
+
+ QMatrix4x4& projectedRotate(qreal angle, qreal x, qreal y, qreal z);
+
+ friend class QGraphicsRotation;
};
inline QMatrix4x4::QMatrix4x4
diff --git a/tests/auto/qgraphicstransform/tst_qgraphicstransform.cpp b/tests/auto/qgraphicstransform/tst_qgraphicstransform.cpp
index eb5c09959f..d8ab06edf2 100644
--- a/tests/auto/qgraphicstransform/tst_qgraphicstransform.cpp
+++ b/tests/auto/qgraphicstransform/tst_qgraphicstransform.cpp
@@ -59,6 +59,8 @@ private slots:
void rotation();
void rotation3d_data();
void rotation3d();
+ void rotation3dArbitraryAxis_data();
+ void rotation3dArbitraryAxis();
};
@@ -88,7 +90,7 @@ static QTransform transform2D(const QGraphicsTransform& t)
{
QMatrix4x4 m;
t.applyTo(&m);
- return m.toTransform(0);
+ return m.toTransform();
}
void tst_QGraphicsTransform::scale()
@@ -255,6 +257,19 @@ void tst_QGraphicsTransform::rotation3d()
QVERIFY(fuzzyCompare(transform2D(rotation), expected));
+ // Check that "rotation" produces the 4x4 form of the 3x3 matrix.
+ // i.e. third row and column are 0 0 1 0.
+ t.setIdentity();
+ rotation.applyTo(&t);
+ QMatrix4x4 r(expected);
+ if (sizeof(qreal) == sizeof(float) && angle == 268) {
+ // This test fails, on only this angle, when qreal == float
+ // because the deg2rad value in QTransform is not accurate
+ // enough to match what QMatrix4x4 is doing.
+ } else {
+ QVERIFY(qFuzzyCompare(t, r));
+ }
+
//now let's check that a null vector will not change the transform
rotation.setAxis(QVector3D(0, 0, 0));
rotation.setOrigin(QVector3D(10, 10, 0));
@@ -276,6 +291,58 @@ void tst_QGraphicsTransform::rotation3d()
QVERIFY(transform2D(rotation).isIdentity());
}
+void tst_QGraphicsTransform::rotation3dArbitraryAxis_data()
+{
+ QTest::addColumn<QVector3D>("axis");
+ QTest::addColumn<qreal>("angle");
+
+ QVector3D axis1 = QVector3D(1.0f, 1.0f, 1.0f);
+ QVector3D axis2 = QVector3D(2.0f, -3.0f, 0.5f);
+ QVector3D axis3 = QVector3D(-2.0f, 0.0f, -0.5f);
+ QVector3D axis4 = QVector3D(0.0001f, 0.0001f, 0.0001f);
+ QVector3D axis5 = QVector3D(0.01f, 0.01f, 0.01f);
+
+ for (int angle = 0; angle <= 360; angle++) {
+ QTest::newRow("test rotation on (1, 1, 1)") << axis1 << qreal(angle);
+ QTest::newRow("test rotation on (2, -3, .5)") << axis2 << qreal(angle);
+ QTest::newRow("test rotation on (-2, 0, -.5)") << axis3 << qreal(angle);
+ QTest::newRow("test rotation on (.0001, .0001, .0001)") << axis4 << qreal(angle);
+ QTest::newRow("test rotation on (.01, .01, .01)") << axis5 << qreal(angle);
+ }
+}
+
+void tst_QGraphicsTransform::rotation3dArbitraryAxis()
+{
+ QFETCH(QVector3D, axis);
+ QFETCH(qreal, angle);
+
+ QGraphicsRotation rotation;
+ rotation.setAxis(axis);
+
+ QMatrix4x4 t;
+ rotation.applyTo(&t);
+
+ QVERIFY(t.isIdentity());
+ QVERIFY(transform2D(rotation).isIdentity());
+
+ rotation.setAngle(angle);
+
+ // Compute the expected answer using QMatrix4x4 and a projection.
+ // These two steps are performed in one hit by QGraphicsRotation.
+ QMatrix4x4 exp;
+ exp.rotate(angle, axis);
+ QTransform expected = exp.toTransform(1024.0f);
+
+ QVERIFY(fuzzyCompare(transform2D(rotation), expected));
+
+ // Check that "rotation" produces the 4x4 form of the 3x3 matrix.
+ // i.e. third row and column are 0 0 1 0.
+ t.setIdentity();
+ rotation.applyTo(&t);
+ QMatrix4x4 r(expected);
+ QVERIFY(qFuzzyCompare(t, r));
+}
+
QTEST_MAIN(tst_QGraphicsTransform)
#include "tst_qgraphicstransform.moc"