Optimized QMatrix4x4 by improving how 'flagBits' are used.
authorKim Motoyoshi Kalland <kim.kalland@nokia.com>
Fri, 19 Aug 2011 13:00:41 +0000 (15:00 +0200)
committerQt by Nokia <qt-info@nokia.com>
Mon, 29 Aug 2011 08:16:01 +0000 (10:16 +0200)
Change-Id: Ic417336489d334e25b547c719d457faf65307cac
Reviewed-on: http://codereview.qt.nokia.com/3670
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Gunnar Sletta <gunnar.sletta@nokia.com>
src/gui/math3d/qmatrix4x4.cpp
src/gui/math3d/qmatrix4x4.h
tests/auto/qmatrixnxn/tst_qmatrixnxn.cpp

index cef92af..1797564 100644 (file)
@@ -209,7 +209,7 @@ QMatrix4x4::QMatrix4x4(const QMatrix& matrix)
     m[3][1] = matrix.dy();
     m[3][2] = 0.0f;
     m[3][3] = 1.0f;
-    flagBits = General;
+    flagBits = Translation | Scale | Rotation2D;
 }
 
 /*!
@@ -316,6 +316,12 @@ QMatrix4x4::QMatrix4x4(const QTransform& transform)
     Fills all elements of this matrx with \a value.
 */
 
+static inline qreal matrixDet2(const qreal m[4][4], int col0, int col1, int row0, int row1)
+{
+    return m[col0][row0] * m[col1][row1] - m[col0][row1] * m[col1][row0];
+}
+
+
 // The 4x4 matrix inverse algorithm is based on that described at:
 // http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q24
 // Some optimization has been done to avoid making copies of 3x3
@@ -329,15 +335,9 @@ static inline qreal matrixDet3
     (const qreal m[4][4], int col0, int col1, int col2,
      int row0, int row1, int row2)
 {
-    return m[col0][row0] *
-                (m[col1][row1] * m[col2][row2] -
-                 m[col1][row2] * m[col2][row1]) -
-           m[col1][row0] *
-                (m[col0][row1] * m[col2][row2] -
-                 m[col0][row2] * m[col2][row1]) +
-           m[col2][row0] *
-                (m[col0][row1] * m[col1][row2] -
-                 m[col0][row2] * m[col1][row1]);
+    return m[col0][row0] * matrixDet2(m, col1, col2, row1, row2)
+            - m[col1][row0] * matrixDet2(m, col0, col2, row1, row2)
+            + m[col2][row0] * matrixDet2(m, col0, col1, row1, row2);
 }
 
 // Calculate the determinant of a 4x4 matrix.
@@ -356,7 +356,13 @@ static inline qreal matrixDet4(const qreal m[4][4])
 */
 qreal QMatrix4x4::determinant() const
 {
-    return qreal(matrixDet4(m));
+    if ((flagBits & ~(Translation | Rotation2D | Rotation)) == Identity)
+        return 1;
+    if (flagBits < Rotation2D)
+        return m[0][0] * m[1][1] * m[2][2]; // Translation | Scale
+    if (flagBits < Perspective)
+        return matrixDet3(m, 0, 1, 2, 0, 1, 2);
+    return matrixDet4(m);
 }
 
 /*!
@@ -387,10 +393,61 @@ QMatrix4x4 QMatrix4x4::inverted(bool *invertible) const
         if (invertible)
             *invertible = true;
         return inv;
-    } else if (flagBits == Rotation || flagBits == (Rotation | Translation)) {
+    } else if (flagBits < Rotation2D) {
+        // Translation | Scale
+        if (m[0][0] == 0 || m[1][1] == 0 || m[2][2] == 0) {
+            if (invertible)
+                *invertible = false;
+            return QMatrix4x4();
+        }
+        QMatrix4x4 inv;
+        inv.m[0][0] = 1.0f / m[0][0];
+        inv.m[1][1] = 1.0f / m[1][1];
+        inv.m[2][2] = 1.0f / m[2][2];
+        inv.m[3][0] = -m[3][0] * inv.m[0][0];
+        inv.m[3][1] = -m[3][1] * inv.m[1][1];
+        inv.m[3][2] = -m[3][2] * inv.m[2][2];
+        inv.flagBits = flagBits;
+
+        if (invertible)
+            *invertible = true;
+        return inv;
+    } else if ((flagBits & ~(Translation | Rotation2D | Rotation)) == Identity) {
         if (invertible)
             *invertible = true;
         return orthonormalInverse();
+    } else if (flagBits < Perspective) {
+        QMatrix4x4 inv(1); // The "1" says to not load the identity.
+
+        qreal det = matrixDet3(m, 0, 1, 2, 0, 1, 2);
+        if (det == 0.0f) {
+            if (invertible)
+                *invertible = false;
+            return QMatrix4x4();
+        }
+        det = 1.0f / det;
+
+        inv.m[0][0] =  matrixDet2(m, 1, 2, 1, 2) * det;
+        inv.m[0][1] = -matrixDet2(m, 0, 2, 1, 2) * det;
+        inv.m[0][2] =  matrixDet2(m, 0, 1, 1, 2) * det;
+        inv.m[0][3] = 0;
+        inv.m[1][0] = -matrixDet2(m, 1, 2, 0, 2) * det;
+        inv.m[1][1] =  matrixDet2(m, 0, 2, 0, 2) * det;
+        inv.m[1][2] = -matrixDet2(m, 0, 1, 0, 2) * det;
+        inv.m[1][3] = 0;
+        inv.m[2][0] =  matrixDet2(m, 1, 2, 0, 1) * det;
+        inv.m[2][1] = -matrixDet2(m, 0, 2, 0, 1) * det;
+        inv.m[2][2] =  matrixDet2(m, 0, 1, 0, 1) * det;
+        inv.m[2][3] = 0;
+        inv.m[3][0] = -inv.m[0][0] * m[3][0] - inv.m[1][0] * m[3][1] - inv.m[2][0] * m[3][2];
+        inv.m[3][1] = -inv.m[0][1] * m[3][0] - inv.m[1][1] * m[3][1] - inv.m[2][1] * m[3][2];
+        inv.m[3][2] = -inv.m[0][2] * m[3][0] - inv.m[1][2] * m[3][1] - inv.m[2][2] * m[3][2];
+        inv.m[3][3] = 1;
+        inv.flagBits = flagBits;
+
+        if (invertible)
+            *invertible = true;
+        return inv;
     }
 
     QMatrix4x4 inv(1); // The "1" says to not load the identity.
@@ -419,6 +476,7 @@ QMatrix4x4 QMatrix4x4::inverted(bool *invertible) const
     inv.m[3][1] =  matrixDet3(m, 0, 2, 3, 0, 1, 2) * det;
     inv.m[3][2] = -matrixDet3(m, 0, 1, 3, 0, 1, 2) * det;
     inv.m[3][3] =  matrixDet3(m, 0, 1, 2, 0, 1, 2) * det;
+    inv.flagBits = flagBits;
 
     if (invertible)
         *invertible = true;
@@ -438,15 +496,29 @@ QMatrix3x3 QMatrix4x4::normalMatrix() const
     QMatrix3x3 inv;
 
     // Handle the simple cases first.
-    if (flagBits == Identity || flagBits == Translation) {
+    if (flagBits < Scale) {
+        // Translation
         return inv;
-    } else if (flagBits == Scale || flagBits == (Translation | Scale)) {
+    } else if (flagBits < Rotation2D) {
+        // Translation | Scale
         if (m[0][0] == 0.0f || m[1][1] == 0.0f || m[2][2] == 0.0f)
             return inv;
         inv.data()[0] = 1.0f / m[0][0];
         inv.data()[4] = 1.0f / m[1][1];
         inv.data()[8] = 1.0f / m[2][2];
         return inv;
+    } else if ((flagBits & ~(Translation | Rotation2D | Rotation)) == Identity) {
+        qreal *invm = inv.data();
+        invm[0 + 0 * 3] = m[0][0];
+        invm[1 + 0 * 3] = m[0][1];
+        invm[2 + 0 * 3] = m[0][2];
+        invm[0 + 1 * 3] = m[1][0];
+        invm[1 + 1 * 3] = m[1][1];
+        invm[2 + 1 * 3] = m[1][2];
+        invm[0 + 2 * 3] = m[2][0];
+        invm[1 + 2 * 3] = m[2][1];
+        invm[2 + 2 * 3] = m[2][2];
+        return inv;
     }
 
     qreal det = matrixDet3(m, 0, 1, 2, 0, 1, 2);
@@ -481,6 +553,8 @@ QMatrix4x4 QMatrix4x4::transposed() const
             result.m[col][row] = m[row][col];
         }
     }
+    // When a translation is transposed, it becomes a perspective transformation.
+    result.flagBits = (flagBits & Translation ? General : flagBits);
     return result;
 }
 
@@ -689,6 +763,7 @@ QMatrix4x4 operator/(const QMatrix4x4& matrix, qreal divisor)
     m.m[3][1] = matrix.m[3][1] / divisor;
     m.m[3][2] = matrix.m[3][2] / divisor;
     m.m[3][3] = matrix.m[3][3] / divisor;
+    m.flagBits = QMatrix4x4::General;
     return m;
 }
 
@@ -713,20 +788,20 @@ void QMatrix4x4::scale(const QVector3D& vector)
     qreal vx = vector.x();
     qreal vy = vector.y();
     qreal vz = vector.z();
-    if (flagBits == Identity) {
+    if (flagBits < Scale) {
         m[0][0] = vx;
         m[1][1] = vy;
         m[2][2] = vz;
-        flagBits = Scale;
-    } else if (flagBits == Scale || flagBits == (Scale | Translation)) {
+    } else if (flagBits < Rotation2D) {
         m[0][0] *= vx;
         m[1][1] *= vy;
         m[2][2] *= vz;
-    } else if (flagBits == Translation) {
-        m[0][0] = vx;
-        m[1][1] = vy;
-        m[2][2] = vz;
-        flagBits |= Scale;
+    } else if (flagBits < Rotation) {
+        m[0][0] *= vx;
+        m[0][1] *= vx;
+        m[1][0] *= vy;
+        m[1][1] *= vy;
+        m[2][2] *= vz;
     } else {
         m[0][0] *= vx;
         m[0][1] *= vx;
@@ -740,9 +815,10 @@ void QMatrix4x4::scale(const QVector3D& vector)
         m[2][1] *= vz;
         m[2][2] *= vz;
         m[2][3] *= vz;
-        flagBits = General;
     }
+    flagBits |= Scale;
 }
+
 #endif
 
 /*!
@@ -755,17 +831,17 @@ void QMatrix4x4::scale(const QVector3D& vector)
 */
 void QMatrix4x4::scale(qreal x, qreal y)
 {
-    if (flagBits == Identity) {
+    if (flagBits < Scale) {
         m[0][0] = x;
         m[1][1] = y;
-        flagBits = Scale;
-    } else if (flagBits == Scale || flagBits == (Scale | Translation)) {
+    } else if (flagBits < Rotation2D) {
         m[0][0] *= x;
         m[1][1] *= y;
-    } else if (flagBits == Translation) {
-        m[0][0] = x;
-        m[1][1] = y;
-        flagBits |= Scale;
+    } else if (flagBits < Rotation) {
+        m[0][0] *= x;
+        m[0][1] *= x;
+        m[1][0] *= y;
+        m[1][1] *= y;
     } else {
         m[0][0] *= x;
         m[0][1] *= x;
@@ -775,8 +851,8 @@ void QMatrix4x4::scale(qreal x, qreal y)
         m[1][1] *= y;
         m[1][2] *= y;
         m[1][3] *= y;
-        flagBits = General;
     }
+    flagBits |= Scale;
 }
 
 /*!
@@ -789,20 +865,20 @@ void QMatrix4x4::scale(qreal x, qreal y)
 */
 void QMatrix4x4::scale(qreal x, qreal y, qreal z)
 {
-    if (flagBits == Identity) {
+    if (flagBits < Scale) {
         m[0][0] = x;
         m[1][1] = y;
         m[2][2] = z;
-        flagBits = Scale;
-    } else if (flagBits == Scale || flagBits == (Scale | Translation)) {
+    } else if (flagBits < Rotation2D) {
         m[0][0] *= x;
         m[1][1] *= y;
         m[2][2] *= z;
-    } else if (flagBits == Translation) {
-        m[0][0] = x;
-        m[1][1] = y;
-        m[2][2] = z;
-        flagBits |= Scale;
+    } else if (flagBits < Rotation) {
+        m[0][0] *= x;
+        m[0][1] *= x;
+        m[1][0] *= y;
+        m[1][1] *= y;
+        m[2][2] *= z;
     } else {
         m[0][0] *= x;
         m[0][1] *= x;
@@ -816,8 +892,8 @@ void QMatrix4x4::scale(qreal x, qreal y, qreal z)
         m[2][1] *= z;
         m[2][2] *= z;
         m[2][3] *= z;
-        flagBits = General;
     }
+    flagBits |= Scale;
 }
 
 /*!
@@ -830,20 +906,20 @@ void QMatrix4x4::scale(qreal x, qreal y, qreal z)
 */
 void QMatrix4x4::scale(qreal factor)
 {
-    if (flagBits == Identity) {
+    if (flagBits < Scale) {
         m[0][0] = factor;
         m[1][1] = factor;
         m[2][2] = factor;
-        flagBits = Scale;
-    } else if (flagBits == Scale || flagBits == (Scale | Translation)) {
+    } else if (flagBits < Rotation2D) {
         m[0][0] *= factor;
         m[1][1] *= factor;
         m[2][2] *= factor;
-    } else if (flagBits == Translation) {
-        m[0][0] = factor;
-        m[1][1] = factor;
-        m[2][2] = factor;
-        flagBits |= Scale;
+    } else if (flagBits < Rotation) {
+        m[0][0] *= factor;
+        m[0][1] *= factor;
+        m[1][0] *= factor;
+        m[1][1] *= factor;
+        m[2][2] *= factor;
     } else {
         m[0][0] *= factor;
         m[0][1] *= factor;
@@ -857,8 +933,8 @@ void QMatrix4x4::scale(qreal factor)
         m[2][1] *= factor;
         m[2][2] *= factor;
         m[2][3] *= factor;
-        flagBits = General;
     }
+    flagBits |= Scale;
 }
 
 #ifndef QT_NO_VECTOR3D
@@ -868,6 +944,7 @@ void QMatrix4x4::scale(qreal factor)
 
     \sa scale(), rotate()
 */
+
 void QMatrix4x4::translate(const QVector3D& vector)
 {
     qreal vx = vector.x();
@@ -877,7 +954,6 @@ void QMatrix4x4::translate(const QVector3D& vector)
         m[3][0] = vx;
         m[3][1] = vy;
         m[3][2] = vz;
-        flagBits = Translation;
     } else if (flagBits == Translation) {
         m[3][0] += vx;
         m[3][1] += vy;
@@ -886,23 +962,22 @@ void QMatrix4x4::translate(const QVector3D& vector)
         m[3][0] = m[0][0] * vx;
         m[3][1] = m[1][1] * vy;
         m[3][2] = m[2][2] * vz;
-        flagBits |= Translation;
-    } else if (flagBits == (Scale | Translation)) {
+    } else if (flagBits == (Translation | Scale)) {
         m[3][0] += m[0][0] * vx;
         m[3][1] += m[1][1] * vy;
         m[3][2] += m[2][2] * vz;
+    } else if (flagBits < Rotation) {
+        m[3][0] += m[0][0] * vx + m[1][0] * vy;
+        m[3][1] += m[0][1] * vx + m[1][1] * vy;
+        m[3][2] += m[2][2] * vz;
     } else {
         m[3][0] += m[0][0] * vx + m[1][0] * vy + m[2][0] * vz;
         m[3][1] += m[0][1] * vx + m[1][1] * vy + m[2][1] * vz;
         m[3][2] += m[0][2] * vx + m[1][2] * vy + m[2][2] * vz;
         m[3][3] += m[0][3] * vx + m[1][3] * vy + m[2][3] * vz;
-        if (flagBits == Rotation)
-            flagBits |= Translation;
-        else if (flagBits != (Rotation | Translation))
-            flagBits = General;
     }
+    flagBits |= Translation;
 }
-
 #endif
 
 /*!
@@ -918,28 +993,25 @@ void QMatrix4x4::translate(qreal x, qreal y)
     if (flagBits == Identity) {
         m[3][0] = x;
         m[3][1] = y;
-        flagBits = Translation;
     } else if (flagBits == Translation) {
         m[3][0] += x;
         m[3][1] += y;
     } else if (flagBits == Scale) {
         m[3][0] = m[0][0] * x;
         m[3][1] = m[1][1] * y;
-        m[3][2] = 0.;
-        flagBits |= Translation;
-    } else if (flagBits == (Scale | Translation)) {
+    } else if (flagBits == (Translation | Scale)) {
         m[3][0] += m[0][0] * x;
         m[3][1] += m[1][1] * y;
+    } else if (flagBits < Rotation) {
+        m[3][0] += m[0][0] * x + m[1][0] * y;
+        m[3][1] += m[0][1] * x + m[1][1] * y;
     } else {
         m[3][0] += m[0][0] * x + m[1][0] * y;
         m[3][1] += m[0][1] * x + m[1][1] * y;
         m[3][2] += m[0][2] * x + m[1][2] * y;
         m[3][3] += m[0][3] * x + m[1][3] * y;
-        if (flagBits == Rotation)
-            flagBits |= Translation;
-        else if (flagBits != (Rotation | Translation))
-            flagBits = General;
     }
+    flagBits |= Translation;
 }
 
 /*!
@@ -956,7 +1028,6 @@ void QMatrix4x4::translate(qreal x, qreal y, qreal z)
         m[3][0] = x;
         m[3][1] = y;
         m[3][2] = z;
-        flagBits = Translation;
     } else if (flagBits == Translation) {
         m[3][0] += x;
         m[3][1] += y;
@@ -965,31 +1036,31 @@ void QMatrix4x4::translate(qreal x, qreal y, qreal z)
         m[3][0] = m[0][0] * x;
         m[3][1] = m[1][1] * y;
         m[3][2] = m[2][2] * z;
-        flagBits |= Translation;
-    } else if (flagBits == (Scale | Translation)) {
+    } else if (flagBits == (Translation | Scale)) {
         m[3][0] += m[0][0] * x;
         m[3][1] += m[1][1] * y;
         m[3][2] += m[2][2] * z;
+    } else if (flagBits < Rotation) {
+        m[3][0] += m[0][0] * x + m[1][0] * y;
+        m[3][1] += m[0][1] * x + m[1][1] * y;
+        m[3][2] += m[2][2] * z;
     } else {
         m[3][0] += m[0][0] * x + m[1][0] * y + m[2][0] * z;
         m[3][1] += m[0][1] * x + m[1][1] * y + m[2][1] * z;
         m[3][2] += m[0][2] * x + m[1][2] * y + m[2][2] * z;
         m[3][3] += m[0][3] * x + m[1][3] * y + m[2][3] * z;
-        if (flagBits == Rotation)
-            flagBits |= Translation;
-        else if (flagBits != (Rotation | Translation))
-            flagBits = General;
     }
+    flagBits |= Translation;
 }
 
 #ifndef QT_NO_VECTOR3D
-
 /*!
     Multiples this matrix by another that rotates coordinates through
     \a angle degrees about \a vector.
 
     \sa scale(), translate()
 */
+
 void QMatrix4x4::rotate(qreal angle, const QVector3D& vector)
 {
     rotate(angle, vector.x(), vector.y(), vector.z());
@@ -1009,8 +1080,7 @@ void QMatrix4x4::rotate(qreal angle, qreal x, qreal y, qreal z)
 {
     if (angle == 0.0f)
         return;
-    QMatrix4x4 m(1); // The "1" says to not load the identity.
-    qreal c, s, ic;
+    qreal c, s;
     if (angle == 90.0f || angle == -270.0f) {
         s = 1.0f;
         c = 0.0f;
@@ -1025,86 +1095,87 @@ void QMatrix4x4::rotate(qreal angle, qreal x, qreal y, qreal z)
         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.setToIdentity();
-                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;
+                if (z < 0)
+                    s = -s;
+                qreal tmp;
+                m[0][0] = (tmp = m[0][0]) * c + m[1][0] * s;
+                m[1][0] = m[1][0] * c - tmp * s;
+                m[0][1] = (tmp = m[0][1]) * c + m[1][1] * s;
+                m[1][1] = m[1][1] * c - tmp * s;
+                m[0][2] = (tmp = m[0][2]) * c + m[1][2] * s;
+                m[1][2] = m[1][2] * c - tmp * s;
+                m[0][3] = (tmp = m[0][3]) * c + m[1][3] * s;
+                m[1][3] = m[1][3] * c - tmp * s;
+
+                flagBits |= Rotation2D;
+                return;
             }
         } else if (z == 0.0f) {
             // Rotate around the Y axis.
-            m.setToIdentity();
-            m.m[0][0] = c;
-            m.m[2][2] = c;
-            if (y < 0.0f) {
-                m.m[2][0] = -s;
-                m.m[0][2] = s;
-            } else {
-                m.m[2][0] = s;
-                m.m[0][2] = -s;
-            }
-            m.flagBits = General;
-            quick = true;
+            if (y < 0)
+                s = -s;
+            qreal tmp;
+            m[2][0] = (tmp = m[2][0]) * c + m[0][0] * s;
+            m[0][0] = m[0][0] * c - tmp * s;
+            m[2][1] = (tmp = m[2][1]) * c + m[0][1] * s;
+            m[0][1] = m[0][1] * c - tmp * s;
+            m[2][2] = (tmp = m[2][2]) * c + m[0][2] * s;
+            m[0][2] = m[0][2] * c - tmp * s;
+            m[2][3] = (tmp = m[2][3]) * c + m[0][3] * s;
+            m[0][3] = m[0][3] * c - tmp * s;
+
+            flagBits |= Rotation;
+            return;
         }
     } else if (y == 0.0f && z == 0.0f) {
         // Rotate around the X axis.
-        m.setToIdentity();
-        m.m[1][1] = c;
-        m.m[2][2] = c;
-        if (x < 0.0f) {
-            m.m[2][1] = s;
-            m.m[1][2] = -s;
-        } else {
-            m.m[2][1] = -s;
-            m.m[1][2] = s;
-        }
-        m.flagBits = General;
-        quick = true;
+        if (x < 0)
+            s = -s;
+        qreal tmp;
+        m[1][0] = (tmp = m[1][0]) * c + m[2][0] * s;
+        m[2][0] = m[2][0] * c - tmp * s;
+        m[1][1] = (tmp = m[1][1]) * c + m[2][1] * s;
+        m[2][1] = m[2][1] * c - tmp * s;
+        m[1][2] = (tmp = m[1][2]) * c + m[2][2] * s;
+        m[2][2] = m[2][2] * c - tmp * s;
+        m[1][3] = (tmp = m[1][3]) * c + m[2][3] * s;
+        m[2][3] = m[2][3] * c - tmp * s;
+
+        flagBits |= Rotation;
+        return;
     }
-    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] = x * z * ic + y * s;
-        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] = y * z * ic - x * s;
-        m.m[3][1] = 0.0f;
-        m.m[0][2] = x * z * ic - y * s;
-        m.m[1][2] = y * z * ic + x * s;
-        m.m[2][2] = z * z * ic + c;
-        m.m[3][2] = 0.0f;
-        m.m[0][3] = 0.0f;
-        m.m[1][3] = 0.0f;
-        m.m[2][3] = 0.0f;
-        m.m[3][3] = 1.0f;
+
+    qreal len = x * x + y * y + z * z;
+    if (!qFuzzyCompare(len, qreal(1)) && !qFuzzyIsNull(len)) {
+        len = qSqrt(len);
+        x /= len;
+        y /= len;
+        z /= len;
     }
-    int flags = flagBits;
-    *this *= m;
-    if (flags != Identity)
-        flagBits = flags | Rotation;
-    else
-        flagBits = Rotation;
+    qreal ic = 1.0f - c;
+    QMatrix4x4 rot(1); // The "1" says to not load the identity.
+    rot.m[0][0] = x * x * ic + c;
+    rot.m[1][0] = x * y * ic - z * s;
+    rot.m[2][0] = x * z * ic + y * s;
+    rot.m[3][0] = 0.0f;
+    rot.m[0][1] = y * x * ic + z * s;
+    rot.m[1][1] = y * y * ic + c;
+    rot.m[2][1] = y * z * ic - x * s;
+    rot.m[3][1] = 0.0f;
+    rot.m[0][2] = x * z * ic - y * s;
+    rot.m[1][2] = y * z * ic + x * s;
+    rot.m[2][2] = z * z * ic + c;
+    rot.m[3][2] = 0.0f;
+    rot.m[0][3] = 0.0f;
+    rot.m[1][3] = 0.0f;
+    rot.m[2][3] = 0.0f;
+    rot.m[3][3] = 1.0f;
+    rot.flagBits = Rotation;
+    *this *= rot;
 }
 
 /*!
@@ -1116,8 +1187,7 @@ void QMatrix4x4::projectedRotate(qreal angle, qreal x, qreal y, qreal z)
     // and projection back to 2D in a single step.
     if (angle == 0.0f)
         return;
-    QMatrix4x4 m(1); // The "1" says to not load the identity.
-    qreal c, s, ic;
+    qreal c, s;
     if (angle == 90.0f || angle == -270.0f) {
         s = 1.0f;
         c = 0.0f;
@@ -1132,82 +1202,74 @@ void QMatrix4x4::projectedRotate(qreal angle, qreal x, qreal y, qreal z)
         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.setToIdentity();
-                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;
+                if (z < 0)
+                    s = -s;
+                qreal tmp;
+                m[0][0] = (tmp = m[0][0]) * c + m[1][0] * s;
+                m[1][0] = m[1][0] * c - tmp * s;
+                m[0][1] = (tmp = m[0][1]) * c + m[1][1] * s;
+                m[1][1] = m[1][1] * c - tmp * s;
+                m[0][2] = (tmp = m[0][2]) * c + m[1][2] * s;
+                m[1][2] = m[1][2] * c - tmp * s;
+                m[0][3] = (tmp = m[0][3]) * c + m[1][3] * s;
+                m[1][3] = m[1][3] * c - tmp * s;
+
+                flagBits |= Rotation2D;
+                return;
             }
         } else if (z == 0.0f) {
             // Rotate around the Y axis.
-            m.setToIdentity();
-            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;
+            if (y < 0)
+                s = -s;
+            m[0][0] = m[0][0] * c + m[3][0] * s * inv_dist_to_plane;
+            m[0][1] = m[0][1] * c + m[3][1] * s * inv_dist_to_plane;
+            m[0][2] = m[0][2] * c + m[3][2] * s * inv_dist_to_plane;
+            m[0][3] = m[0][3] * c + m[3][3] * s * inv_dist_to_plane;
+            flagBits = General;
+            return;
         }
     } else if (y == 0.0f && z == 0.0f) {
         // Rotate around the X axis.
-        m.setToIdentity();
-        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 (x < 0)
+            s = -s;
+        m[1][0] = m[1][0] * c - m[3][0] * s * inv_dist_to_plane;
+        m[1][1] = m[1][1] * c - m[3][1] * s * inv_dist_to_plane;
+        m[1][2] = m[1][2] * c - m[3][2] * s * inv_dist_to_plane;
+        m[1][3] = m[1][3] * c - m[3][3] * s * inv_dist_to_plane;
+        flagBits = General;
+        return;
     }
-    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;
+    qreal len = x * x + y * y + z * z;
+    if (!qFuzzyIsNull(len - 1.0f) && !qFuzzyIsNull(len)) {
+        len = qSqrt(len);
+        x /= len;
+        y /= len;
+        z /= len;
     }
-    int flags = flagBits;
-    *this *= m;
-    if (flags != Identity)
-        flagBits = flags | Rotation;
-    else
-        flagBits = Rotation;
+    qreal ic = 1.0f - c;
+    QMatrix4x4 rot(1); // The "1" says to not load the identity.
+    rot.m[0][0] = x * x * ic + c;
+    rot.m[1][0] = x * y * ic - z * s;
+    rot.m[2][0] = 0.0f;
+    rot.m[3][0] = 0.0f;
+    rot.m[0][1] = y * x * ic + z * s;
+    rot.m[1][1] = y * y * ic + c;
+    rot.m[2][1] = 0.0f;
+    rot.m[3][1] = 0.0f;
+    rot.m[0][2] = 0.0f;
+    rot.m[1][2] = 0.0f;
+    rot.m[2][2] = 1.0f;
+    rot.m[3][2] = 0.0f;
+    rot.m[0][3] = (x * z * ic - y * s) * -inv_dist_to_plane;
+    rot.m[1][3] = (y * z * ic + x * s) * -inv_dist_to_plane;
+    rot.m[2][3] = 0.0f;
+    rot.m[3][3] = 1.0f;
+    rot.flagBits = General;
+    *this *= rot;
 }
 
 #ifndef QT_NO_QUATERNION
@@ -1249,12 +1311,8 @@ void QMatrix4x4::rotate(const QQuaternion& quaternion)
     m.m[1][3] = 0.0f;
     m.m[2][3] = 0.0f;
     m.m[3][3] = 1.0f;
-    int flags = flagBits;
+    m.flagBits = Rotation;
     *this *= m;
-    if (flags != Identity)
-        flagBits = flags | Rotation;
-    else
-        flagBits = Rotation;
 }
 
 #endif
@@ -1309,22 +1367,6 @@ void QMatrix4x4::ortho(qreal left, qreal right, qreal bottom, qreal top, qreal n
     qreal width = right - left;
     qreal invheight = top - bottom;
     qreal clip = farPlane - nearPlane;
-#ifndef QT_NO_VECTOR3D
-    if (clip == 2.0f && (nearPlane + farPlane) == 0.0f) {
-        // We can express this projection as a translate and scale
-        // which will be more efficient to modify with further
-        // transformations than producing a "General" matrix.
-        translate(QVector3D
-            (-(left + right) / width,
-             -(top + bottom) / invheight,
-             0.0f));
-        scale(QVector3D
-            (2.0f / width,
-             2.0f / invheight,
-             -1.0f));
-        return;
-    }
-#endif
     QMatrix4x4 m(1);
     m.m[0][0] = 2.0f / width;
     m.m[1][0] = 0.0f;
@@ -1342,10 +1384,10 @@ void QMatrix4x4::ortho(qreal left, qreal right, qreal bottom, qreal top, qreal n
     m.m[1][3] = 0.0f;
     m.m[2][3] = 0.0f;
     m.m[3][3] = 1.0f;
+    m.flagBits = Translation | Scale;
 
     // Apply the projection.
     *this *= m;
-    return;
 }
 
 /*!
@@ -1383,6 +1425,7 @@ void QMatrix4x4::frustum(qreal left, qreal right, qreal bottom, qreal top, qreal
     m.m[1][3] = 0.0f;
     m.m[2][3] = -1.0f;
     m.m[3][3] = 0.0f;
+    m.flagBits = General;
 
     // Apply the projection.
     *this *= m;
@@ -1426,6 +1469,7 @@ void QMatrix4x4::perspective(qreal angle, qreal aspect, qreal nearPlane, qreal f
     m.m[1][3] = 0.0f;
     m.m[2][3] = -1.0f;
     m.m[3][3] = 0.0f;
+    m.flagBits = General;
 
     // Apply the projection.
     *this *= m;
@@ -1446,7 +1490,6 @@ void QMatrix4x4::lookAt(const QVector3D& eye, const QVector3D& center, const QVe
     QVector3D upVector = QVector3D::crossProduct(side, forward);
 
     QMatrix4x4 m(1);
-
     m.m[0][0] = side.x();
     m.m[1][0] = side.y();
     m.m[2][0] = side.z();
@@ -1463,6 +1506,7 @@ void QMatrix4x4::lookAt(const QVector3D& eye, const QVector3D& center, const QVe
     m.m[1][3] = 0.0f;
     m.m[2][3] = 0.0f;
     m.m[3][3] = 1.0f;
+    m.flagBits = Rotation;
 
     *this *= m;
     translate(-eye);
@@ -1471,6 +1515,8 @@ void QMatrix4x4::lookAt(const QVector3D& eye, const QVector3D& center, const QVe
 #endif
 
 /*!
+    \deprecated
+
     Flips between right-handed and left-handed coordinate systems
     by multiplying the y and z co-ordinates by -1.  This is normally
     used to create a left-handed orthographic view without scaling
@@ -1480,17 +1526,13 @@ void QMatrix4x4::lookAt(const QVector3D& eye, const QVector3D& center, const QVe
 */
 void QMatrix4x4::flipCoordinates()
 {
-    if (flagBits == Scale || flagBits == (Scale | Translation)) {
+    // Multiplying the y and z coordinates with -1 does NOT flip between right-handed and
+    // left-handed coordinate systems, it just rotates 180 degrees around the x axis, so
+    // I'm deprecating this function.
+    if (flagBits < Rotation2D) {
+        // Translation | Scale
         m[1][1] = -m[1][1];
         m[2][2] = -m[2][2];
-    } else if (flagBits == Translation) {
-        m[1][1] = -m[1][1];
-        m[2][2] = -m[2][2];
-        flagBits |= Scale;
-    } else if (flagBits == Identity) {
-        m[1][1] = -1.0f;
-        m[2][2] = -1.0f;
-        flagBits = Scale;
     } else {
         m[1][0] = -m[1][0];
         m[1][1] = -m[1][1];
@@ -1500,8 +1542,8 @@ void QMatrix4x4::flipCoordinates()
         m[2][1] = -m[2][1];
         m[2][2] = -m[2][2];
         m[2][3] = -m[2][3];
-        flagBits = General;
     }
+    flagBits |= Scale;
 }
 
 /*!
@@ -1568,12 +1610,9 @@ QTransform QMatrix4x4::toTransform(qreal distanceToPlane) const
 {
     if (distanceToPlane == 1024.0f) {
         // Optimize the common case with constants.
-        return QTransform(m[0][0], m[0][1],
-                                m[0][3] - m[0][2] * inv_dist_to_plane,
-                          m[1][0], m[1][1],
-                                m[1][3] - m[1][2] * inv_dist_to_plane,
-                          m[3][0], m[3][1],
-                                m[3][3] - m[3][2] * inv_dist_to_plane);
+        return QTransform(m[0][0], m[0][1], m[0][3] - m[0][2] * inv_dist_to_plane,
+                          m[1][0], m[1][1], m[1][3] - m[1][2] * inv_dist_to_plane,
+                          m[3][0], m[3][1], m[3][3] - m[3][2] * inv_dist_to_plane);
     } else if (distanceToPlane != 0.0f) {
         // The following projection matrix is pre-multiplied with "matrix":
         //      | 1 0 0 0 |
@@ -1654,7 +1693,13 @@ QTransform QMatrix4x4::toTransform(qreal distanceToPlane) const
 */
 QRect QMatrix4x4::mapRect(const QRect& rect) const
 {
-    if (flagBits == (Translation | Scale) || flagBits == Scale) {
+    if (flagBits < Scale) {
+        // Translation
+        return QRect(qRound(rect.x() + m[3][0]),
+                     qRound(rect.y() + m[3][1]),
+                     rect.width(), rect.height());
+    } else if (flagBits < Rotation2D) {
+        // Translation | Scale
         qreal x = rect.x() * m[0][0] + m[3][0];
         qreal y = rect.y() * m[1][1] + m[3][1];
         qreal w = rect.width() * m[0][0];
@@ -1668,10 +1713,6 @@ QRect QMatrix4x4::mapRect(const QRect& rect) const
             y -= h;
         }
         return QRect(qRound(x), qRound(y), qRound(w), qRound(h));
-    } else if (flagBits == Translation) {
-        return QRect(qRound(rect.x() + m[3][0]),
-                     qRound(rect.y() + m[3][1]),
-                     rect.width(), rect.height());
     }
 
     QPoint tl = map(rect.topLeft());
@@ -1698,7 +1739,11 @@ QRect QMatrix4x4::mapRect(const QRect& rect) const
 */
 QRectF QMatrix4x4::mapRect(const QRectF& rect) const
 {
-    if (flagBits == (Translation | Scale) || flagBits == Scale) {
+    if (flagBits < Scale) {
+        // Translation
+        return rect.translated(m[3][0], m[3][1]);
+    } else if (flagBits < Rotation2D) {
+        // Translation | Scale
         qreal x = rect.x() * m[0][0] + m[3][0];
         qreal y = rect.y() * m[1][1] + m[3][1];
         qreal w = rect.width() * m[0][0];
@@ -1712,8 +1757,6 @@ QRectF QMatrix4x4::mapRect(const QRectF& rect) const
             y -= h;
         }
         return QRectF(x, y, w, h);
-    } else if (flagBits == Translation) {
-        return rect.translated(m[3][0], m[3][1]);
     }
 
     QPointF tl = map(rect.topLeft()); QPointF tr = map(rect.topRight());
@@ -1780,6 +1823,8 @@ QMatrix4x4 QMatrix4x4::orthonormalInverse() const
     result.m[3][2] = -(result.m[0][2] * m[3][0] + result.m[1][2] * m[3][1] + result.m[2][2] * m[3][2]);
     result.m[3][3] = 1.0f;
 
+    result.flagBits = flagBits;
+
     return result;
 }
 
@@ -1805,40 +1850,50 @@ QMatrix4x4 QMatrix4x4::orthonormalInverse() const
 */
 void QMatrix4x4::optimize()
 {
-    // If the last element is not 1, then it can never be special.
-    if (m[3][3] != 1.0f) {
-        flagBits = General;
+    // If the last row is not (0, 0, 0, 1), the matrix is not a special type.
+    flagBits = General;
+    if (m[0][3] != 0 || m[1][3] != 0 || m[2][3] != 0 || m[3][3] != 1)
         return;
-    }
 
-    // If the upper three elements m12, m13, and m21 are not all zero,
-    // or the lower elements below the diagonal are not all zero, then
-    // the matrix can never be special.
-    if (m[1][0] != 0.0f || m[2][0] != 0.0f || m[2][1] != 0.0f) {
-        flagBits = General;
-        return;
-    }
-    if (m[0][1] != 0.0f || m[0][2] != 0.0f || m[0][3] != 0.0f ||
-        m[1][2] != 0.0f || m[1][3] != 0.0f || m[2][3] != 0.0f) {
-        flagBits = General;
-        return;
+    flagBits &= ~Perspective;
+
+    // If the last column is (0, 0, 0, 1), then there is no translation.
+    if (m[3][0] == 0 && m[3][1] == 0 && m[3][2] == 0)
+        flagBits &= ~Translation;
+
+    // If the two first elements of row 3 and column 3 are 0, then any rotation must be about Z.
+    if (!m[0][2] && !m[1][2] && !m[2][0] && !m[2][1]) {
+        flagBits &= ~Rotation;
+        // If the six non-diagonal elements in the top left 3x3 matrix are 0, there is no rotation.
+        if (!m[0][1] && !m[1][0]) {
+            flagBits &= ~Rotation2D;
+            // Check for identity.
+            if (m[0][0] == 1 && m[1][1] == 1 && m[2][2] == 1)
+                flagBits &= ~Scale;
+        } else {
+            // If the columns are orthonormal and form a right-handed system, then there is no scale.
+            qreal det = matrixDet2(m, 0, 1, 0, 1);
+            qreal lenX = m[0][0] * m[0][0] + m[0][1] * m[0][1];
+            qreal lenY = m[1][0] * m[1][0] + m[1][1] * m[1][1];
+            qreal lenZ = m[2][2];
+            if (qFuzzyCompare(det, qreal(1)) && qFuzzyCompare(lenX, qreal(1))
+                    && qFuzzyCompare(lenY, qreal(1)) && qFuzzyCompare(lenZ, qreal(1)))
+            {
+                flagBits &= ~Scale;
+            }
+        }
+    } else {
+        // If the columns are orthonormal and form a right-handed system, then there is no scale.
+        qreal det = matrixDet3(m, 0, 1, 2, 0, 1, 2);
+        qreal lenX = m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2];
+        qreal lenY = m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2];
+        qreal lenZ = m[2][0] * m[2][0] + m[2][1] * m[2][1] + m[2][2] * m[2][2];
+        if (qFuzzyCompare(det, qreal(1)) && qFuzzyCompare(lenX, qreal(1))
+                && qFuzzyCompare(lenY, qreal(1)) && qFuzzyCompare(lenZ, qreal(1)))
+        {
+            flagBits &= ~Scale;
+        }
     }
-
-    // Determine what we have in the remaining regions of the matrix.
-    bool identityAlongDiagonal
-        = (m[0][0] == 1.0f && m[1][1] == 1.0f && m[2][2] == 1.0f);
-    bool translationPresent
-        = (m[3][0] != 0.0f || m[3][1] != 0.0f || m[3][2] != 0.0f);
-
-    // Now determine the special matrix type.
-    if (translationPresent && identityAlongDiagonal)
-        flagBits = Translation;
-    else if (translationPresent)
-        flagBits = (Translation | Scale);
-    else if (identityAlongDiagonal)
-        flagBits = Identity;
-    else
-        flagBits = Scale;
 }
 
 /*!
@@ -1855,18 +1910,24 @@ QDebug operator<<(QDebug dbg, const QMatrix4x4 &m)
 {
     // Create a string that represents the matrix type.
     QByteArray bits;
-    if ((m.flagBits & QMatrix4x4::Identity) != 0)
-        bits += "Identity,";
-    if ((m.flagBits & QMatrix4x4::General) != 0)
-        bits += "General,";
-    if ((m.flagBits & QMatrix4x4::Translation) != 0)
-        bits += "Translation,";
-    if ((m.flagBits & QMatrix4x4::Scale) != 0)
-        bits += "Scale,";
-    if ((m.flagBits & QMatrix4x4::Rotation) != 0)
-        bits += "Rotation,";
-    if (bits.size() > 0)
-        bits = bits.left(bits.size() - 1);
+    if (m.flagBits == QMatrix4x4::Identity) {
+        bits = "Identity";
+    } else if (m.flagBits == QMatrix4x4::General) {
+        bits = "General";
+    } else {
+        if ((m.flagBits & QMatrix4x4::Translation) != 0)
+            bits += "Translation,";
+        if ((m.flagBits & QMatrix4x4::Scale) != 0)
+            bits += "Scale,";
+        if ((m.flagBits & QMatrix4x4::Rotation2D) != 0)
+            bits += "Rotation2D,";
+        if ((m.flagBits & QMatrix4x4::Rotation) != 0)
+            bits += "Rotation,";
+        if ((m.flagBits & QMatrix4x4::Perspective) != 0)
+            bits += "Perspective,";
+        if (bits.size() > 0)
+            bits = bits.left(bits.size() - 1);
+    }
 
     // Output in row-major order because it is more human-readable.
     dbg.nospace() << "QMatrix4x4(type:" << bits.constData() << endl
index bfbe4b9..2c98dd0 100644 (file)
@@ -80,11 +80,13 @@ public:
     inline const qreal& operator()(int row, int column) const;
     inline qreal& operator()(int row, int column);
 
+#ifndef QT_NO_VECTOR4D
     inline QVector4D column(int index) const;
     inline void setColumn(int index, const QVector4D& value);
 
     inline QVector4D row(int index) const;
     inline void setRow(int index, const QVector4D& value);
+#endif
 
     inline bool isIdentity() const;
     inline void setToIdentity();
@@ -188,16 +190,19 @@ private:
     qreal m[4][4];          // Column-major order to match OpenGL.
     int flagBits;           // Flag bits from the enum below.
 
+    // When matrices are multiplied, the flag bits are or-ed together.
     enum {
-        Identity        = 0x0001,   // Identity matrix
-        General         = 0x0002,   // General matrix, unknown contents
-        Translation     = 0x0004,   // Contains a simple translation
-        Scale           = 0x0008,   // Contains a simple scale
-        Rotation        = 0x0010    // Contains a simple rotation
+        Identity        = 0x0000, // Identity matrix
+        Translation     = 0x0001, // Contains a translation
+        Scale           = 0x0002, // Contains a scale
+        Rotation2D      = 0x0004, // Contains a rotation about the Z axis
+        Rotation        = 0x0008, // Contains an arbitrary rotation
+        Perspective     = 0x0010, // Last row is different from (0, 0, 0, 1)
+        General         = 0x001f  // General matrix, unknown contents
     };
 
     // Construct without initializing identity matrix.
-    QMatrix4x4(int) { flagBits = General; }
+    QMatrix4x4(int) { }
 
     QMatrix4x4 orthonormalInverse() const;
 
@@ -270,6 +275,7 @@ inline qreal& QMatrix4x4::operator()(int aRow, int aColumn)
     return m[aColumn][aRow];
 }
 
+#ifndef QT_NO_VECTOR4D
 inline QVector4D QMatrix4x4::column(int index) const
 {
     Q_ASSERT(index >= 0 && index < 4);
@@ -301,6 +307,7 @@ inline void QMatrix4x4::setRow(int index, const QVector4D& value)
     m[3][index] = value.w();
     flagBits = General;
 }
+#endif
 
 Q_GUI_EXPORT QMatrix4x4 operator/(const QMatrix4x4& matrix, qreal divisor);
 
@@ -409,15 +416,100 @@ inline QMatrix4x4& QMatrix4x4::operator-=(const QMatrix4x4& other)
 
 inline QMatrix4x4& QMatrix4x4::operator*=(const QMatrix4x4& other)
 {
-    if (flagBits == Identity) {
-        *this = other;
-        return *this;
-    } else if (other.flagBits == Identity) {
-        return *this;
-    } else {
-        *this = *this * other;
+    flagBits |= other.flagBits;
+
+    if (flagBits < Rotation2D) {
+        m[3][0] += m[0][0] * other.m[3][0];
+        m[3][1] += m[1][1] * other.m[3][1];
+        m[3][2] += m[2][2] * other.m[3][2];
+
+        m[0][0] *= other.m[0][0];
+        m[1][1] *= other.m[1][1];
+        m[2][2] *= other.m[2][2];
         return *this;
     }
+
+    qreal m0, m1, m2;
+    m0 = m[0][0] * other.m[0][0]
+            + m[1][0] * other.m[0][1]
+            + m[2][0] * other.m[0][2]
+            + m[3][0] * other.m[0][3];
+    m1 = m[0][0] * other.m[1][0]
+            + m[1][0] * other.m[1][1]
+            + m[2][0] * other.m[1][2]
+            + m[3][0] * other.m[1][3];
+    m2 = m[0][0] * other.m[2][0]
+            + m[1][0] * other.m[2][1]
+            + m[2][0] * other.m[2][2]
+            + m[3][0] * other.m[2][3];
+    m[3][0] = m[0][0] * other.m[3][0]
+            + m[1][0] * other.m[3][1]
+            + m[2][0] * other.m[3][2]
+            + m[3][0] * other.m[3][3];
+    m[0][0] = m0;
+    m[1][0] = m1;
+    m[2][0] = m2;
+
+    m0 = m[0][1] * other.m[0][0]
+            + m[1][1] * other.m[0][1]
+            + m[2][1] * other.m[0][2]
+            + m[3][1] * other.m[0][3];
+    m1 = m[0][1] * other.m[1][0]
+            + m[1][1] * other.m[1][1]
+            + m[2][1] * other.m[1][2]
+            + m[3][1] * other.m[1][3];
+    m2 = m[0][1] * other.m[2][0]
+            + m[1][1] * other.m[2][1]
+            + m[2][1] * other.m[2][2]
+            + m[3][1] * other.m[2][3];
+    m[3][1] = m[0][1] * other.m[3][0]
+            + m[1][1] * other.m[3][1]
+            + m[2][1] * other.m[3][2]
+            + m[3][1] * other.m[3][3];
+    m[0][1] = m0;
+    m[1][1] = m1;
+    m[2][1] = m2;
+
+    m0 = m[0][2] * other.m[0][0]
+            + m[1][2] * other.m[0][1]
+            + m[2][2] * other.m[0][2]
+            + m[3][2] * other.m[0][3];
+    m1 = m[0][2] * other.m[1][0]
+            + m[1][2] * other.m[1][1]
+            + m[2][2] * other.m[1][2]
+            + m[3][2] * other.m[1][3];
+    m2 = m[0][2] * other.m[2][0]
+            + m[1][2] * other.m[2][1]
+            + m[2][2] * other.m[2][2]
+            + m[3][2] * other.m[2][3];
+    m[3][2] = m[0][2] * other.m[3][0]
+            + m[1][2] * other.m[3][1]
+            + m[2][2] * other.m[3][2]
+            + m[3][2] * other.m[3][3];
+    m[0][2] = m0;
+    m[1][2] = m1;
+    m[2][2] = m2;
+
+    m0 = m[0][3] * other.m[0][0]
+            + m[1][3] * other.m[0][1]
+            + m[2][3] * other.m[0][2]
+            + m[3][3] * other.m[0][3];
+    m1 = m[0][3] * other.m[1][0]
+            + m[1][3] * other.m[1][1]
+            + m[2][3] * other.m[1][2]
+            + m[3][3] * other.m[1][3];
+    m2 = m[0][3] * other.m[2][0]
+            + m[1][3] * other.m[2][1]
+            + m[2][3] * other.m[2][2]
+            + m[3][3] * other.m[2][3];
+    m[3][3] = m[0][3] * other.m[3][0]
+            + m[1][3] * other.m[3][1]
+            + m[2][3] * other.m[3][2]
+            + m[3][3] * other.m[3][3];
+    m[0][3] = m0;
+    m[1][3] = m1;
+    m[2][3] = m2;
+    return *this;
 }
 
 inline QMatrix4x4& QMatrix4x4::operator*=(qreal factor)
@@ -501,6 +593,7 @@ inline QMatrix4x4 operator+(const QMatrix4x4& m1, const QMatrix4x4& m2)
     m.m[3][1] = m1.m[3][1] + m2.m[3][1];
     m.m[3][2] = m1.m[3][2] + m2.m[3][2];
     m.m[3][3] = m1.m[3][3] + m2.m[3][3];
+    m.flagBits = QMatrix4x4::General;
     return m;
 }
 
@@ -523,81 +616,95 @@ inline QMatrix4x4 operator-(const QMatrix4x4& m1, const QMatrix4x4& m2)
     m.m[3][1] = m1.m[3][1] - m2.m[3][1];
     m.m[3][2] = m1.m[3][2] - m2.m[3][2];
     m.m[3][3] = m1.m[3][3] - m2.m[3][3];
+    m.flagBits = QMatrix4x4::General;
     return m;
 }
 
 inline QMatrix4x4 operator*(const QMatrix4x4& m1, const QMatrix4x4& m2)
 {
-    if (m1.flagBits == QMatrix4x4::Identity)
-        return m2;
-    else if (m2.flagBits == QMatrix4x4::Identity)
-        return m1;
+    int flagBits = m1.flagBits | m2.flagBits;
+    if (flagBits < QMatrix4x4::Rotation2D) {
+        QMatrix4x4 m = m1;
+        m.m[3][0] += m.m[0][0] * m2.m[3][0];
+        m.m[3][1] += m.m[1][1] * m2.m[3][1];
+        m.m[3][2] += m.m[2][2] * m2.m[3][2];
+
+        m.m[0][0] *= m2.m[0][0];
+        m.m[1][1] *= m2.m[1][1];
+        m.m[2][2] *= m2.m[2][2];
+        m.flagBits = flagBits;
+        return m;
+    }
 
     QMatrix4x4 m(1);
-    m.m[0][0] = m1.m[0][0] * m2.m[0][0] +
-                m1.m[1][0] * m2.m[0][1] +
-                m1.m[2][0] * m2.m[0][2] +
-                m1.m[3][0] * m2.m[0][3];
-    m.m[0][1] = m1.m[0][1] * m2.m[0][0] +
-                m1.m[1][1] * m2.m[0][1] +
-                m1.m[2][1] * m2.m[0][2] +
-                m1.m[3][1] * m2.m[0][3];
-    m.m[0][2] = m1.m[0][2] * m2.m[0][0] +
-                m1.m[1][2] * m2.m[0][1] +
-                m1.m[2][2] * m2.m[0][2] +
-                m1.m[3][2] * m2.m[0][3];
-    m.m[0][3] = m1.m[0][3] * m2.m[0][0] +
-                m1.m[1][3] * m2.m[0][1] +
-                m1.m[2][3] * m2.m[0][2] +
-                m1.m[3][3] * m2.m[0][3];
-    m.m[1][0] = m1.m[0][0] * m2.m[1][0] +
-                m1.m[1][0] * m2.m[1][1] +
-                m1.m[2][0] * m2.m[1][2] +
-                m1.m[3][0] * m2.m[1][3];
-    m.m[1][1] = m1.m[0][1] * m2.m[1][0] +
-                m1.m[1][1] * m2.m[1][1] +
-                m1.m[2][1] * m2.m[1][2] +
-                m1.m[3][1] * m2.m[1][3];
-    m.m[1][2] = m1.m[0][2] * m2.m[1][0] +
-                m1.m[1][2] * m2.m[1][1] +
-                m1.m[2][2] * m2.m[1][2] +
-                m1.m[3][2] * m2.m[1][3];
-    m.m[1][3] = m1.m[0][3] * m2.m[1][0] +
-                m1.m[1][3] * m2.m[1][1] +
-                m1.m[2][3] * m2.m[1][2] +
-                m1.m[3][3] * m2.m[1][3];
-    m.m[2][0] = m1.m[0][0] * m2.m[2][0] +
-                m1.m[1][0] * m2.m[2][1] +
-                m1.m[2][0] * m2.m[2][2] +
-                m1.m[3][0] * m2.m[2][3];
-    m.m[2][1] = m1.m[0][1] * m2.m[2][0] +
-                m1.m[1][1] * m2.m[2][1] +
-                m1.m[2][1] * m2.m[2][2] +
-                m1.m[3][1] * m2.m[2][3];
-    m.m[2][2] = m1.m[0][2] * m2.m[2][0] +
-                m1.m[1][2] * m2.m[2][1] +
-                m1.m[2][2] * m2.m[2][2] +
-                m1.m[3][2] * m2.m[2][3];
-    m.m[2][3] = m1.m[0][3] * m2.m[2][0] +
-                m1.m[1][3] * m2.m[2][1] +
-                m1.m[2][3] * m2.m[2][2] +
-                m1.m[3][3] * m2.m[2][3];
-    m.m[3][0] = m1.m[0][0] * m2.m[3][0] +
-                m1.m[1][0] * m2.m[3][1] +
-                m1.m[2][0] * m2.m[3][2] +
-                m1.m[3][0] * m2.m[3][3];
-    m.m[3][1] = m1.m[0][1] * m2.m[3][0] +
-                m1.m[1][1] * m2.m[3][1] +
-                m1.m[2][1] * m2.m[3][2] +
-                m1.m[3][1] * m2.m[3][3];
-    m.m[3][2] = m1.m[0][2] * m2.m[3][0] +
-                m1.m[1][2] * m2.m[3][1] +
-                m1.m[2][2] * m2.m[3][2] +
-                m1.m[3][2] * m2.m[3][3];
-    m.m[3][3] = m1.m[0][3] * m2.m[3][0] +
-                m1.m[1][3] * m2.m[3][1] +
-                m1.m[2][3] * m2.m[3][2] +
-                m1.m[3][3] * m2.m[3][3];
+    m.m[0][0] = m1.m[0][0] * m2.m[0][0]
+              + m1.m[1][0] * m2.m[0][1]
+              + m1.m[2][0] * m2.m[0][2]
+              + m1.m[3][0] * m2.m[0][3];
+    m.m[0][1] = m1.m[0][1] * m2.m[0][0]
+              + m1.m[1][1] * m2.m[0][1]
+              + m1.m[2][1] * m2.m[0][2]
+              + m1.m[3][1] * m2.m[0][3];
+    m.m[0][2] = m1.m[0][2] * m2.m[0][0]
+              + m1.m[1][2] * m2.m[0][1]
+              + m1.m[2][2] * m2.m[0][2]
+              + m1.m[3][2] * m2.m[0][3];
+    m.m[0][3] = m1.m[0][3] * m2.m[0][0]
+              + m1.m[1][3] * m2.m[0][1]
+              + m1.m[2][3] * m2.m[0][2]
+              + m1.m[3][3] * m2.m[0][3];
+
+    m.m[1][0] = m1.m[0][0] * m2.m[1][0]
+              + m1.m[1][0] * m2.m[1][1]
+              + m1.m[2][0] * m2.m[1][2]
+              + m1.m[3][0] * m2.m[1][3];
+    m.m[1][1] = m1.m[0][1] * m2.m[1][0]
+              + m1.m[1][1] * m2.m[1][1]
+              + m1.m[2][1] * m2.m[1][2]
+              + m1.m[3][1] * m2.m[1][3];
+    m.m[1][2] = m1.m[0][2] * m2.m[1][0]
+              + m1.m[1][2] * m2.m[1][1]
+              + m1.m[2][2] * m2.m[1][2]
+              + m1.m[3][2] * m2.m[1][3];
+    m.m[1][3] = m1.m[0][3] * m2.m[1][0]
+              + m1.m[1][3] * m2.m[1][1]
+              + m1.m[2][3] * m2.m[1][2]
+              + m1.m[3][3] * m2.m[1][3];
+
+    m.m[2][0] = m1.m[0][0] * m2.m[2][0]
+              + m1.m[1][0] * m2.m[2][1]
+              + m1.m[2][0] * m2.m[2][2]
+              + m1.m[3][0] * m2.m[2][3];
+    m.m[2][1] = m1.m[0][1] * m2.m[2][0]
+              + m1.m[1][1] * m2.m[2][1]
+              + m1.m[2][1] * m2.m[2][2]
+              + m1.m[3][1] * m2.m[2][3];
+    m.m[2][2] = m1.m[0][2] * m2.m[2][0]
+              + m1.m[1][2] * m2.m[2][1]
+              + m1.m[2][2] * m2.m[2][2]
+              + m1.m[3][2] * m2.m[2][3];
+    m.m[2][3] = m1.m[0][3] * m2.m[2][0]
+              + m1.m[1][3] * m2.m[2][1]
+              + m1.m[2][3] * m2.m[2][2]
+              + m1.m[3][3] * m2.m[2][3];
+
+    m.m[3][0] = m1.m[0][0] * m2.m[3][0]
+              + m1.m[1][0] * m2.m[3][1]
+              + m1.m[2][0] * m2.m[3][2]
+              + m1.m[3][0] * m2.m[3][3];
+    m.m[3][1] = m1.m[0][1] * m2.m[3][0]
+              + m1.m[1][1] * m2.m[3][1]
+              + m1.m[2][1] * m2.m[3][2]
+              + m1.m[3][1] * m2.m[3][3];
+    m.m[3][2] = m1.m[0][2] * m2.m[3][0]
+              + m1.m[1][2] * m2.m[3][1]
+              + m1.m[2][2] * m2.m[3][2]
+              + m1.m[3][2] * m2.m[3][3];
+    m.m[3][3] = m1.m[0][3] * m2.m[3][0]
+              + m1.m[1][3] * m2.m[3][1]
+              + m1.m[2][3] * m2.m[3][2]
+              + m1.m[3][3] * m2.m[3][3];
+    m.flagBits = flagBits;
     return m;
 }
 
@@ -633,19 +740,16 @@ inline QVector3D operator*(const QMatrix4x4& matrix, const QVector3D& vector)
     qreal x, y, z, w;
     if (matrix.flagBits == QMatrix4x4::Identity) {
         return vector;
-    } else if (matrix.flagBits == QMatrix4x4::Translation) {
-        return QVector3D(vector.x() + matrix.m[3][0],
-                         vector.y() + matrix.m[3][1],
-                         vector.z() + matrix.m[3][2]);
-    } else if (matrix.flagBits ==
-                    (QMatrix4x4::Translation | QMatrix4x4::Scale)) {
+    } else if (matrix.flagBits < QMatrix4x4::Rotation2D) {
+        // Translation | Scale
         return QVector3D(vector.x() * matrix.m[0][0] + matrix.m[3][0],
                          vector.y() * matrix.m[1][1] + matrix.m[3][1],
                          vector.z() * matrix.m[2][2] + matrix.m[3][2]);
-    } else if (matrix.flagBits == QMatrix4x4::Scale) {
-        return QVector3D(vector.x() * matrix.m[0][0],
-                         vector.y() * matrix.m[1][1],
-                         vector.z() * matrix.m[2][2]);
+    } else if (matrix.flagBits < QMatrix4x4::Rotation) {
+        // Translation | Scale | Rotation2D
+        return QVector3D(vector.x() * matrix.m[0][0] + vector.y() * matrix.m[1][0] + matrix.m[3][0],
+                         vector.x() * matrix.m[0][1] + vector.y() * matrix.m[1][1] + matrix.m[3][1],
+                         vector.z() * matrix.m[2][2] + matrix.m[3][2]);
     } else {
         x = vector.x() * matrix.m[0][0] +
             vector.y() * matrix.m[1][0] +
@@ -771,16 +875,13 @@ inline QPoint operator*(const QMatrix4x4& matrix, const QPoint& point)
     yin = point.y();
     if (matrix.flagBits == QMatrix4x4::Identity) {
         return point;
-    } else if (matrix.flagBits == QMatrix4x4::Translation) {
-        return QPoint(qRound(xin + matrix.m[3][0]),
-                      qRound(yin + matrix.m[3][1]));
-    } else if (matrix.flagBits ==
-                    (QMatrix4x4::Translation | QMatrix4x4::Scale)) {
+    } else if (matrix.flagBits < QMatrix4x4::Rotation2D) {
+        // Translation | Scale
         return QPoint(qRound(xin * matrix.m[0][0] + matrix.m[3][0]),
                       qRound(yin * matrix.m[1][1] + matrix.m[3][1]));
-    } else if (matrix.flagBits == QMatrix4x4::Scale) {
-        return QPoint(qRound(xin * matrix.m[0][0]),
-                      qRound(yin * matrix.m[1][1]));
+    } else if (matrix.flagBits < QMatrix4x4::Perspective) {
+        return QPoint(qRound(xin * matrix.m[0][0] + yin * matrix.m[1][0] + matrix.m[3][0]),
+                      qRound(xin * matrix.m[0][1] + yin * matrix.m[1][1] + matrix.m[3][1]));
     } else {
         x = xin * matrix.m[0][0] +
             yin * matrix.m[1][0] +
@@ -806,16 +907,13 @@ inline QPointF operator*(const QMatrix4x4& matrix, const QPointF& point)
     yin = point.y();
     if (matrix.flagBits == QMatrix4x4::Identity) {
         return point;
-    } else if (matrix.flagBits == QMatrix4x4::Translation) {
-        return QPointF(xin + matrix.m[3][0],
-                       yin + matrix.m[3][1]);
-    } else if (matrix.flagBits ==
-                    (QMatrix4x4::Translation | QMatrix4x4::Scale)) {
+    } else if (matrix.flagBits < QMatrix4x4::Rotation2D) {
+        // Translation | Scale
         return QPointF(xin * matrix.m[0][0] + matrix.m[3][0],
                        yin * matrix.m[1][1] + matrix.m[3][1]);
-    } else if (matrix.flagBits == QMatrix4x4::Scale) {
-        return QPointF(xin * matrix.m[0][0],
-                       yin * matrix.m[1][1]);
+    } else if (matrix.flagBits < QMatrix4x4::Perspective) {
+        return QPointF(xin * matrix.m[0][0] + yin * matrix.m[1][0] + matrix.m[3][0],
+                       xin * matrix.m[0][1] + yin * matrix.m[1][1] + matrix.m[3][1]);
     } else {
         x = xin * matrix.m[0][0] +
             yin * matrix.m[1][0] +
@@ -853,6 +951,7 @@ inline QMatrix4x4 operator-(const QMatrix4x4& matrix)
     m.m[3][1] = -matrix.m[3][1];
     m.m[3][2] = -matrix.m[3][2];
     m.m[3][3] = -matrix.m[3][3];
+    m.flagBits = QMatrix4x4::General;
     return m;
 }
 
@@ -875,6 +974,7 @@ inline QMatrix4x4 operator*(qreal factor, const QMatrix4x4& matrix)
     m.m[3][1] = matrix.m[3][1] * factor;
     m.m[3][2] = matrix.m[3][2] * factor;
     m.m[3][3] = matrix.m[3][3] * factor;
+    m.flagBits = QMatrix4x4::General;
     return m;
 }
 
@@ -897,6 +997,7 @@ inline QMatrix4x4 operator*(const QMatrix4x4& matrix, qreal factor)
     m.m[3][1] = matrix.m[3][1] * factor;
     m.m[3][2] = matrix.m[3][2] * factor;
     m.m[3][3] = matrix.m[3][3] * factor;
+    m.flagBits = QMatrix4x4::General;
     return m;
 }
 
@@ -939,9 +1040,11 @@ inline QVector3D QMatrix4x4::map(const QVector3D& point) const
 
 inline QVector3D QMatrix4x4::mapVector(const QVector3D& vector) const
 {
-    if (flagBits == Identity || flagBits == Translation) {
+    if (flagBits < Scale) {
+        // Translation
         return vector;
-    } else if (flagBits == Scale || flagBits == (Translation | Scale)) {
+    } else if (flagBits < Rotation2D) {
+        // Translation | Scale
         return QVector3D(vector.x() * m[0][0],
                          vector.y() * m[1][1],
                          vector.z() * m[2][2]);
index 1b4d770..98a559a 100644 (file)
@@ -1834,6 +1834,18 @@ void tst_QMatrixNxN::inverted4x4_data()
     QTest::newRow("invertible")
         << (void *)invertible.v << (void *)inverted.v << true;
 
+    static Matrix4 const invertible2 = {
+        {1.0f, 2.0f, 4.0f, 2.0f,
+         8.0f, 3.0f, 5.0f, 3.0f,
+         6.0f, 7.0f, 9.0f, 4.0f,
+         0.0f, 0.0f, 0.0f, 1.0f}
+    };
+    static Matrix4 inverted2;
+    m4Inverse(invertible2, inverted2);
+
+    QTest::newRow("invertible2")
+        << (void *)invertible2.v << (void *)inverted2.v << true;
+
     static Matrix4 const translate = {
         {1.0f, 0.0f, 0.0f, 2.0f,
          0.0f, 1.0f, 0.0f, 3.0f,
@@ -1907,12 +1919,12 @@ void tst_QMatrixNxN::orthonormalInverse4x4()
     m2.rotate(45.0, 1.0, 0.0, 0.0);
     m2.translate(10.0, 0.0, 0.0);
 
-    // Use optimize() to drop the internal flags that
+    // Use operator() to drop the internal flags that
     // mark the matrix as orthonormal.  This will force inverted()
     // to compute m3.inverted() the long way.  We can then compare
     // the result to what the faster algorithm produces on m2.
     QMatrix4x4 m3 = m2;
-    m3.optimize();
+    m3(0, 0);
     bool invertible;
     QVERIFY(qFuzzyCompare(m2.inverted(&invertible), m3.inverted()));
     QVERIFY(invertible);
@@ -1920,7 +1932,7 @@ void tst_QMatrixNxN::orthonormalInverse4x4()
     QMatrix4x4 m4;
     m4.rotate(45.0, 0.0, 1.0, 0.0);
     QMatrix4x4 m5 = m4;
-    m5.optimize();
+    m5(0, 0);
     QVERIFY(qFuzzyCompare(m4.inverted(), m5.inverted()));
 
     QMatrix4x4 m6;
@@ -1928,7 +1940,7 @@ void tst_QMatrixNxN::orthonormalInverse4x4()
     m1.translate(-20.0, 20.0, 15.0);
     m1.rotate(25, 1.0, 0.0, 0.0);
     QMatrix4x4 m7 = m6;
-    m7.optimize();
+    m7(0, 0);
     QVERIFY(qFuzzyCompare(m6.inverted(), m7.inverted()));
 }
 
@@ -2449,6 +2461,11 @@ void tst_QMatrixNxN::normalMatrix_data()
          0.0f, 7.0f, 0.0f, 5.0f,
          0.0f, 0.0f, 9.0f, -3.0f,
          0.0f, 0.0f, 0.0f, 1.0f};
+    static qreal const rotateValues[16] =
+        {0.0f, 0.0f, 1.0f, 0.0f,
+         1.0f, 0.0f, 0.0f, 0.0f,
+         0.0f, 1.0f, 0.0f, 0.0f,
+         0.0f, 0.0f, 0.0f, 1.0f};
     static qreal const nullScaleValues1[16] =
         {0.0f, 0.0f, 0.0f, 4.0f,
          0.0f, 7.0f, 0.0f, 5.0f,
@@ -2468,6 +2485,7 @@ void tst_QMatrixNxN::normalMatrix_data()
     QTest::newRow("translate") << (void *)translateValues;
     QTest::newRow("scale") << (void *)scaleValues;
     QTest::newRow("both") << (void *)bothValues;
+    QTest::newRow("rotate") << (void *)rotateValues;
     QTest::newRow("null scale 1") << (void *)nullScaleValues1;
     QTest::newRow("null scale 2") << (void *)nullScaleValues2;
     QTest::newRow("null scale 3") << (void *)nullScaleValues3;
@@ -2844,11 +2862,13 @@ void tst_QMatrixNxN::convertGeneric()
 
 // Copy of "flagBits" in qmatrix4x4.h.
 enum {
-    Identity        = 0x0001,   // Identity matrix
-    General         = 0x0002,   // General matrix, unknown contents
-    Translation     = 0x0004,   // Contains a simple translation
-    Scale           = 0x0008,   // Contains a simple scale
-    Rotation        = 0x0010    // Contains a simple rotation
+    Identity        = 0x0000, // Identity matrix
+    Translation     = 0x0001, // Contains a translation
+    Scale           = 0x0002, // Contains a scale
+    Rotation2D      = 0x0004, // Contains a rotation about the Z axis
+    Rotation        = 0x0008, // Contains an arbitrary rotation
+    Perspective     = 0x0010, // Last row is different from (0, 0, 0, 1)
+    General         = 0x001f  // General matrix, unknown contents
 };
 
 // Structure that allows direct access to "flagBits" for testing.
@@ -2886,17 +2906,73 @@ void tst_QMatrixNxN::optimize_data()
         0.0f, 0.0f, 1.0f, 4.0f,
         0.0f, 0.0f, 0.0f, 1.0f
     };
-    QTest::newRow("scale")
+    QTest::newRow("translate")
         << (void *)translateValues << (int)Translation;
 
-    static qreal bothValues[16] = {
+    static qreal scaleTranslateValues[16] = {
         1.0f, 0.0f, 0.0f, 2.0f,
         0.0f, 2.0f, 0.0f, 0.0f,
         0.0f, 0.0f, 1.0f, 4.0f,
         0.0f, 0.0f, 0.0f, 1.0f
     };
-    QTest::newRow("both")
-        << (void *)bothValues << (int)(Scale | Translation);
+    QTest::newRow("scaleTranslate")
+        << (void *)scaleTranslateValues << (int)(Scale | Translation);
+
+    static qreal rotateValues[16] = {
+        0.0f, 1.0f, 0.0f, 0.0f,
+        -1.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 1.0f, 0.0f,
+        0.0f, 0.0f, 0.0f, 1.0f
+    };
+    QTest::newRow("rotate")
+        << (void *)rotateValues << (int)Rotation2D;
+
+    // Left-handed system, not a simple rotation.
+    static qreal scaleRotateValues[16] = {
+        0.0f, 1.0f, 0.0f, 0.0f,
+        1.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 1.0f, 0.0f,
+        0.0f, 0.0f, 0.0f, 1.0f
+    };
+    QTest::newRow("scaleRotate")
+        << (void *)scaleRotateValues << (int)(Scale | Rotation2D);
+
+    static qreal matrix2x2Values[16] = {
+        1.0f, 2.0f, 0.0f, 0.0f,
+        8.0f, 3.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 9.0f, 0.0f,
+        0.0f, 0.0f, 0.0f, 1.0f
+    };
+    QTest::newRow("matrix2x2")
+        << (void *)matrix2x2Values << (int)(Scale | Rotation2D);
+
+    static qreal matrix3x3Values[16] = {
+        1.0f, 2.0f, 4.0f, 0.0f,
+        8.0f, 3.0f, 5.0f, 0.0f,
+        6.0f, 7.0f, 9.0f, 0.0f,
+        0.0f, 0.0f, 0.0f, 1.0f
+    };
+    QTest::newRow("matrix3x3")
+        << (void *)matrix3x3Values << (int)(Scale | Rotation2D | Rotation);
+
+    static qreal rotateTranslateValues[16] = {
+        0.0f, 1.0f, 0.0f, 1.0f,
+        -1.0f, 0.0f, 0.0f, 2.0f,
+        0.0f, 0.0f, 1.0f, 3.0f,
+        0.0f, 0.0f, 0.0f, 1.0f
+    };
+    QTest::newRow("rotateTranslate")
+        << (void *)rotateTranslateValues << (int)(Translation | Rotation2D);
+
+    // Left-handed system, not a simple rotation.
+    static qreal scaleRotateTranslateValues[16] = {
+        0.0f, 1.0f, 0.0f, 1.0f,
+        1.0f, 0.0f, 0.0f, 2.0f,
+        0.0f, 0.0f, 1.0f, 3.0f,
+        0.0f, 0.0f, 0.0f, 1.0f
+    };
+    QTest::newRow("scaleRotateTranslate")
+        << (void *)scaleRotateTranslateValues << (int)(Translation | Scale | Rotation2D);
 
     static qreal belowValues[16] = {
         1.0f, 0.0f, 0.0f, 0.0f,
@@ -3240,7 +3316,6 @@ void tst_QMatrixNxN::mapVector()
     QFETCH(void *, mValues);
 
     QMatrix4x4 m1((const qreal *)mValues);
-    m1.optimize();
 
     QVector3D v(3.5f, -1.0f, 2.5f);
 
@@ -3250,10 +3325,15 @@ void tst_QMatrixNxN::mapVector()
          v.x() * m1(2, 0) + v.y() * m1(2, 1) + v.z() * m1(2, 2));
 
     QVector3D actual = m1.mapVector(v);
+    m1.optimize();
+    QVector3D actual2 = m1.mapVector(v);
 
     QVERIFY(fuzzyCompare(actual.x(), expected.x()));
     QVERIFY(fuzzyCompare(actual.y(), expected.y()));
     QVERIFY(fuzzyCompare(actual.z(), expected.z()));
+    QVERIFY(fuzzyCompare(actual2.x(), expected.x()));
+    QVERIFY(fuzzyCompare(actual2.y(), expected.y()));
+    QVERIFY(fuzzyCompare(actual2.z(), expected.z()));
 }
 
 class tst_QMatrixNxN4x4Properties : public QObject