diff options
author | Rhys Weatherley <rhys.weatherley@nokia.com> | 2009-08-19 09:47:27 +1000 |
---|---|---|
committer | Rhys Weatherley <rhys.weatherley@nokia.com> | 2009-08-19 09:47:27 +1000 |
commit | f7af55e67711270286a1addf6a28399982647a62 (patch) | |
tree | a4360ce1574e9f02cf83cdc3b71735e8d0b3c344 /tests/auto/qmatrixnxn | |
parent | 4377c9f6a593359b436f7c8186abac8dad772329 (diff) | |
download | qt4-tools-f7af55e67711270286a1addf6a28399982647a62.tar.gz |
Move math3d unit tests up one level to tests/auto
Some of the platform test infrastructure assumes that all unit
tests are sub-directories under tests/auto, and the tests/auto/math3d
sub-directory was confusing that infrastructure.
Reviewed-by: trustme
Diffstat (limited to 'tests/auto/qmatrixnxn')
-rw-r--r-- | tests/auto/qmatrixnxn/qmatrixnxn.pro | 2 | ||||
-rw-r--r-- | tests/auto/qmatrixnxn/tst_qmatrixnxn.cpp | 3383 |
2 files changed, 3385 insertions, 0 deletions
diff --git a/tests/auto/qmatrixnxn/qmatrixnxn.pro b/tests/auto/qmatrixnxn/qmatrixnxn.pro new file mode 100644 index 0000000000..cf6e4a128e --- /dev/null +++ b/tests/auto/qmatrixnxn/qmatrixnxn.pro @@ -0,0 +1,2 @@ +load(qttest_p4) +SOURCES += tst_qmatrixnxn.cpp diff --git a/tests/auto/qmatrixnxn/tst_qmatrixnxn.cpp b/tests/auto/qmatrixnxn/tst_qmatrixnxn.cpp new file mode 100644 index 0000000000..b8c04c0d90 --- /dev/null +++ b/tests/auto/qmatrixnxn/tst_qmatrixnxn.cpp @@ -0,0 +1,3383 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QtCore/qmath.h> +#include <QtGui/qmatrix4x4.h> + +class tst_QMatrixNxN : public QObject +{ + Q_OBJECT +public: + tst_QMatrixNxN() {} + ~tst_QMatrixNxN() {} + +private slots: + void create2x2(); + void create3x3(); + void create4x4(); + void create4x3(); + + void isIdentity2x2(); + void isIdentity3x3(); + void isIdentity4x4(); + void isIdentity4x3(); + + void compare2x2(); + void compare3x3(); + void compare4x4(); + void compare4x3(); + + void transposed2x2(); + void transposed3x3(); + void transposed4x4(); + void transposed4x3(); + + void add2x2_data(); + void add2x2(); + void add3x3_data(); + void add3x3(); + void add4x4_data(); + void add4x4(); + void add4x3_data(); + void add4x3(); + + void subtract2x2_data(); + void subtract2x2(); + void subtract3x3_data(); + void subtract3x3(); + void subtract4x4_data(); + void subtract4x4(); + void subtract4x3_data(); + void subtract4x3(); + + void multiply2x2_data(); + void multiply2x2(); + void multiply3x3_data(); + void multiply3x3(); + void multiply4x4_data(); + void multiply4x4(); + void multiply4x3_data(); + void multiply4x3(); + + void multiplyFactor2x2_data(); + void multiplyFactor2x2(); + void multiplyFactor3x3_data(); + void multiplyFactor3x3(); + void multiplyFactor4x4_data(); + void multiplyFactor4x4(); + void multiplyFactor4x3_data(); + void multiplyFactor4x3(); + + void divideFactor2x2_data(); + void divideFactor2x2(); + void divideFactor3x3_data(); + void divideFactor3x3(); + void divideFactor4x4_data(); + void divideFactor4x4(); + void divideFactor4x3_data(); + void divideFactor4x3(); + + void negate2x2_data(); + void negate2x2(); + void negate3x3_data(); + void negate3x3(); + void negate4x4_data(); + void negate4x4(); + void negate4x3_data(); + void negate4x3(); + + void inverted4x4_data(); + void inverted4x4(); + + void orthonormalInverse4x4(); + + void scale4x4_data(); + void scale4x4(); + + void translate4x4_data(); + void translate4x4(); + + void rotate4x4_data(); + void rotate4x4(); + + void normalMatrix_data(); + void normalMatrix(); + + void optimizedTransforms(); + + void ortho(); + void frustum(); + void perspective(); + void flipCoordinates(); + + void convertGeneric(); + + void extractAxisRotation_data(); + void extractAxisRotation(); + + void extractTranslation_data(); + void extractTranslation(); + + void inferSpecialType_data(); + void inferSpecialType(); + + void columnsAndRows(); + + void convertQMatrix(); + void convertQTransform(); + + void fill(); + + void mapRect_data(); + void mapRect(); + + void properties(); + void metaTypes(); + +private: + static void setMatrix(QMatrix2x2& m, const qreal *values); + static void setMatrixDirect(QMatrix2x2& m, const qreal *values); + static bool isSame(const QMatrix2x2& m, const qreal *values); + static bool isIdentity(const QMatrix2x2& m); + + static void setMatrix(QMatrix3x3& m, const qreal *values); + static void setMatrixDirect(QMatrix3x3& m, const qreal *values); + static bool isSame(const QMatrix3x3& m, const qreal *values); + static bool isIdentity(const QMatrix3x3& m); + + static void setMatrix(QMatrix4x4& m, const qreal *values); + static void setMatrixDirect(QMatrix4x4& m, const qreal *values); + static bool isSame(const QMatrix4x4& m, const qreal *values); + static bool isIdentity(const QMatrix4x4& m); + + static void setMatrix(QMatrix4x3& m, const qreal *values); + static void setMatrixDirect(QMatrix4x3& m, const qreal *values); + static bool isSame(const QMatrix4x3& m, const qreal *values); + static bool isIdentity(const QMatrix4x3& m); +}; + +static const qreal nullValues2[] = + {0.0f, 0.0f, + 0.0f, 0.0f}; + +static qreal const identityValues2[16] = + {1.0f, 0.0f, + 0.0f, 1.0f}; + +static const qreal doubleIdentity2[] = + {2.0f, 0.0f, + 0.0f, 2.0f}; + +static qreal const uniqueValues2[16] = + {1.0f, 2.0f, + 5.0f, 6.0f}; + +static qreal const transposedValues2[16] = + {1.0f, 5.0f, + 2.0f, 6.0f}; + +static const qreal nullValues3[] = + {0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f}; + +static qreal const identityValues3[16] = + {1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f}; + +static const qreal doubleIdentity3[] = + {2.0f, 0.0f, 0.0f, + 0.0f, 2.0f, 0.0f, + 0.0f, 0.0f, 2.0f}; + +static qreal const uniqueValues3[16] = + {1.0f, 2.0f, 3.0f, + 5.0f, 6.0f, 7.0f, + 9.0f, 10.0f, 11.0f}; + +static qreal const transposedValues3[16] = + {1.0f, 5.0f, 9.0f, + 2.0f, 6.0f, 10.0f, + 3.0f, 7.0f, 11.0f}; + +static const qreal nullValues4[] = + {0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f}; + +static qreal const identityValues4[16] = + {1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + +static const qreal doubleIdentity4[] = + {2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 2.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 2.0f}; + +static qreal const uniqueValues4[16] = + {1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f}; + +static qreal const transposedValues4[16] = + {1.0f, 5.0f, 9.0f, 13.0f, + 2.0f, 6.0f, 10.0f, 14.0f, + 3.0f, 7.0f, 11.0f, 15.0f, + 4.0f, 8.0f, 12.0f, 16.0f}; + +static const qreal nullValues4x3[] = + {0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f}; + +static qreal const identityValues4x3[12] = + {1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f}; + +static qreal const doubleIdentity4x3[12] = + {2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 2.0f, 0.0f}; + +static qreal const uniqueValues4x3[12] = + {1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f}; + +static qreal const transposedValues3x4[12] = + {1.0f, 5.0f, 9.0f, + 2.0f, 6.0f, 10.0f, + 3.0f, 7.0f, 11.0f, + 4.0f, 8.0f, 12.0f}; + +// Set a matrix to a specified array of values, which are assumed +// to be in row-major order. This sets the values using floating-point. +void tst_QMatrixNxN::setMatrix(QMatrix2x2& m, const qreal *values) +{ + for (int row = 0; row < 2; ++row) + for (int col = 0; col < 2; ++col) + m(row, col) = values[row * 2 + col]; +} +void tst_QMatrixNxN::setMatrix(QMatrix3x3& m, const qreal *values) +{ + for (int row = 0; row < 3; ++row) + for (int col = 0; col < 3; ++col) + m(row, col) = values[row * 3 + col]; +} +void tst_QMatrixNxN::setMatrix(QMatrix4x4& m, const qreal *values) +{ + for (int row = 0; row < 4; ++row) + for (int col = 0; col < 4; ++col) + m(row, col) = values[row * 4 + col]; +} +void tst_QMatrixNxN::setMatrix(QMatrix4x3& m, const qreal *values) +{ + for (int row = 0; row < 3; ++row) + for (int col = 0; col < 4; ++col) + m(row, col) = values[row * 4 + col]; +} + +// Set a matrix to a specified array of values, which are assumed +// to be in row-major order. This sets the values directly into +// the internal data() array. +void tst_QMatrixNxN::setMatrixDirect(QMatrix2x2& m, const qreal *values) +{ + float *data = m.data(); + for (int row = 0; row < 2; ++row) { + for (int col = 0; col < 2; ++col) { + data[row + col * 2] = values[row * 2 + col]; + } + } +} +void tst_QMatrixNxN::setMatrixDirect(QMatrix3x3& m, const qreal *values) +{ + float *data = m.data(); + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) { + data[row + col * 3] = values[row * 3 + col]; + } + } +} +void tst_QMatrixNxN::setMatrixDirect(QMatrix4x4& m, const qreal *values) +{ + float *data = m.data(); + for (int row = 0; row < 4; ++row) { + for (int col = 0; col < 4; ++col) { + data[row + col * 4] = values[row * 4 + col]; + } + } +} +void tst_QMatrixNxN::setMatrixDirect(QMatrix4x3& m, const qreal *values) +{ + float *data = m.data(); + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 4; ++col) { + data[row + col * 3] = values[row * 4 + col]; + } + } +} + +// qFuzzyCompare isn't always "fuzzy" enough to handle conversion +// between float, double, and qreal. So create "fuzzier" compares. +static bool fuzzyCompare(float x, float y, qreal epsilon = 0.001) +{ + float diff = x - y; + if (diff < 0.0f) + diff = -diff; + return (diff < epsilon); +} + +static bool fuzzyCompare(const QVector3D &v1, const QVector3D &v2, qreal epsilon = 0.001) +{ + if (!fuzzyCompare(v1.x(), v2.x(), epsilon)) + return false; + if (!fuzzyCompare(v1.y(), v2.y(), epsilon)) + return false; + if (!fuzzyCompare(v1.z(), v2.z(), epsilon)) + return false; + return true; +} + +static bool matrixFuzzyCompare(const QMatrix4x4 &m1, const QMatrix4x4 &m2) +{ + bool ret = true; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + ret = ret && fuzzyCompare(m1(i, j), m2(i, j)); + } + } + + return ret; +} + +// Determine if a matrix is the same as a specified array of values. +// The values are assumed to be specified in row-major order. +bool tst_QMatrixNxN::isSame(const QMatrix2x2& m, const qreal *values) +{ + const float *mv = m.constData(); + for (int row = 0; row < 2; ++row) { + for (int col = 0; col < 2; ++col) { + // Check the values using the operator() function. + if (!fuzzyCompare((float)(m(row, col)), (float)(values[row * 2 + col]))) { + qDebug() << "floating-point failure at" << row << col << "actual =" << m(row, col) << "expected =" << values[row * 2 + col]; + return false; + } + + // Check the values using direct access, which verifies that the values + // are stored internally in column-major order. + if (!fuzzyCompare((float)(mv[col * 2 + row]), (float)(values[row * 2 + col]))) { + qDebug() << "column floating-point failure at" << row << col << "actual =" << mv[col * 2 + row] << "expected =" << values[row * 2 + col]; + return false; + } + } + } + return true; +} +bool tst_QMatrixNxN::isSame(const QMatrix3x3& m, const qreal *values) +{ + const float *mv = m.constData(); + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) { + // Check the values using the operator() access function. + if (!fuzzyCompare((float)(m(row, col)), (float)(values[row * 3 + col]))) { + qDebug() << "floating-point failure at" << row << col << "actual =" << m(row, col) << "expected =" << values[row * 3 + col]; + return false; + } + + // Check the values using direct access, which verifies that the values + // are stored internally in column-major order. + if (!fuzzyCompare((float)(mv[col * 3 + row]), (float)(values[row * 3 + col]))) { + qDebug() << "column floating-point failure at" << row << col << "actual =" << mv[col * 3 + row] << "expected =" << values[row * 3 + col]; + return false; + } + } + } + return true; +} +bool tst_QMatrixNxN::isSame(const QMatrix4x4& m, const qreal *values) +{ + const float *mv = m.constData(); + for (int row = 0; row < 4; ++row) { + for (int col = 0; col < 4; ++col) { + // Check the values using the operator() access function. + if (!fuzzyCompare((float)(m(row, col)), (float)(values[row * 4 + col]))) { + qDebug() << "floating-point failure at" << row << col << "actual =" << m(row, col) << "expected =" << values[row * 4 + col]; + return false; + } + + // Check the values using direct access, which verifies that the values + // are stored internally in column-major order. + if (!fuzzyCompare((float)(mv[col * 4 + row]), (float)(values[row * 4 + col]))) { + qDebug() << "column floating-point failure at" << row << col << "actual =" << mv[col * 4 + row] << "expected =" << values[row * 4 + col]; + return false; + } + } + } + return true; +} +bool tst_QMatrixNxN::isSame(const QMatrix4x3& m, const qreal *values) +{ + const float *mv = m.constData(); + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 4; ++col) { + // Check the values using the operator() access function. + if (!fuzzyCompare((float)(m(row, col)), (float)(values[row * 4 + col]))) { + qDebug() << "floating-point failure at" << row << col << "actual =" << m(row, col) << "expected =" << values[row * 4 + col]; + return false; + } + + // Check the values using direct access, which verifies that the values + // are stored internally in column-major order. + if (!fuzzyCompare((float)(mv[col * 3 + row]), (float)(values[row * 4 + col]))) { + qDebug() << "column floating-point failure at" << row << col << "actual =" << mv[col * 3 + row] << "expected =" << values[row * 4 + col]; + return false; + } + } + } + return true; +} + +// Determine if a matrix is the identity. +bool tst_QMatrixNxN::isIdentity(const QMatrix2x2& m) +{ + return isSame(m, identityValues2); +} +bool tst_QMatrixNxN::isIdentity(const QMatrix3x3& m) +{ + return isSame(m, identityValues3); +} +bool tst_QMatrixNxN::isIdentity(const QMatrix4x4& m) +{ + return isSame(m, identityValues4); +} +bool tst_QMatrixNxN::isIdentity(const QMatrix4x3& m) +{ + return isSame(m, identityValues4x3); +} + +// Test the creation of QMatrix2x2 objects in various ways: +// construct, copy, and modify. +void tst_QMatrixNxN::create2x2() +{ + QMatrix2x2 m1; + QVERIFY(isIdentity(m1)); + QVERIFY(m1.isIdentity()); + + QMatrix2x2 m2; + setMatrix(m2, uniqueValues2); + QVERIFY(isSame(m2, uniqueValues2)); + QVERIFY(!m2.isIdentity()); + + QMatrix2x2 m3; + setMatrixDirect(m3, uniqueValues2); + QVERIFY(isSame(m3, uniqueValues2)); + + QMatrix2x2 m4(m3); + QVERIFY(isSame(m4, uniqueValues2)); + + QMatrix2x2 m5; + m5 = m3; + QVERIFY(isSame(m5, uniqueValues2)); + + m5.setIdentity(); + QVERIFY(isIdentity(m5)); + + QMatrix2x2 m6(uniqueValues2); + QVERIFY(isSame(m6, uniqueValues2)); + qreal vals[4]; + m6.toValueArray(vals); + for (int index = 0; index < 4; ++index) + QCOMPARE((float)(vals[index]), (float)(uniqueValues2[index])); +} + +// Test the creation of QMatrix3x3 objects in various ways: +// construct, copy, and modify. +void tst_QMatrixNxN::create3x3() +{ + QMatrix3x3 m1; + QVERIFY(isIdentity(m1)); + QVERIFY(m1.isIdentity()); + + QMatrix3x3 m2; + setMatrix(m2, uniqueValues3); + QVERIFY(isSame(m2, uniqueValues3)); + QVERIFY(!m2.isIdentity()); + + QMatrix3x3 m3; + setMatrixDirect(m3, uniqueValues3); + QVERIFY(isSame(m3, uniqueValues3)); + + QMatrix3x3 m4(m3); + QVERIFY(isSame(m4, uniqueValues3)); + + QMatrix3x3 m5; + m5 = m3; + QVERIFY(isSame(m5, uniqueValues3)); + + m5.setIdentity(); + QVERIFY(isIdentity(m5)); + + QMatrix3x3 m6(uniqueValues3); + QVERIFY(isSame(m6, uniqueValues3)); + qreal vals[9]; + m6.toValueArray(vals); + for (int index = 0; index < 9; ++index) + QCOMPARE((float)(vals[index]), (float)(uniqueValues3[index])); +} + +// Test the creation of QMatrix4x4 objects in various ways: +// construct, copy, and modify. +void tst_QMatrixNxN::create4x4() +{ + QMatrix4x4 m1; + QVERIFY(isIdentity(m1)); + QVERIFY(m1.isIdentity()); + + QMatrix4x4 m2; + setMatrix(m2, uniqueValues4); + QVERIFY(isSame(m2, uniqueValues4)); + QVERIFY(!m2.isIdentity()); + + QMatrix4x4 m3; + setMatrixDirect(m3, uniqueValues4); + QVERIFY(isSame(m3, uniqueValues4)); + + QMatrix4x4 m4(m3); + QVERIFY(isSame(m4, uniqueValues4)); + + QMatrix4x4 m5; + m5 = m3; + QVERIFY(isSame(m5, uniqueValues4)); + + m5.setIdentity(); + QVERIFY(isIdentity(m5)); + + QMatrix4x4 m6(uniqueValues4); + QVERIFY(isSame(m6, uniqueValues4)); + qreal vals[16]; + m6.toValueArray(vals); + for (int index = 0; index < 16; ++index) + QCOMPARE((float)(vals[index]), (float)(uniqueValues4[index])); + + QMatrix4x4 m8 + (uniqueValues4[0], uniqueValues4[1], uniqueValues4[2], uniqueValues4[3], + uniqueValues4[4], uniqueValues4[5], uniqueValues4[6], uniqueValues4[7], + uniqueValues4[8], uniqueValues4[9], uniqueValues4[10], uniqueValues4[11], + uniqueValues4[12], uniqueValues4[13], uniqueValues4[14], uniqueValues4[15]); + QVERIFY(isSame(m8, uniqueValues4)); +} + +// Test the creation of QMatrix4x3 objects in various ways: +// construct, copy, and modify. +void tst_QMatrixNxN::create4x3() +{ + QMatrix4x3 m1; + QVERIFY(isIdentity(m1)); + QVERIFY(m1.isIdentity()); + + QMatrix4x3 m2; + setMatrix(m2, uniqueValues4x3); + QVERIFY(isSame(m2, uniqueValues4x3)); + QVERIFY(!m2.isIdentity()); + + QMatrix4x3 m3; + setMatrixDirect(m3, uniqueValues4x3); + QVERIFY(isSame(m3, uniqueValues4x3)); + + QMatrix4x3 m4(m3); + QVERIFY(isSame(m4, uniqueValues4x3)); + + QMatrix4x3 m5; + m5 = m3; + QVERIFY(isSame(m5, uniqueValues4x3)); + + m5.setIdentity(); + QVERIFY(isIdentity(m5)); + + QMatrix4x3 m6(uniqueValues4x3); + QVERIFY(isSame(m6, uniqueValues4x3)); + qreal vals[12]; + m6.toValueArray(vals); + for (int index = 0; index < 12; ++index) + QCOMPARE((float)(vals[index]), (float)(uniqueValues4x3[index])); +} + +// Test isIdentity() for 2x2 matrices. +void tst_QMatrixNxN::isIdentity2x2() +{ + for (int i = 0; i < 2 * 2; ++i) { + QMatrix2x2 m; + QVERIFY(m.isIdentity()); + m.data()[i] = 42.0f; + QVERIFY(!m.isIdentity()); + } +} + +// Test isIdentity() for 3x3 matrices. +void tst_QMatrixNxN::isIdentity3x3() +{ + for (int i = 0; i < 3 * 3; ++i) { + QMatrix3x3 m; + QVERIFY(m.isIdentity()); + m.data()[i] = 42.0f; + QVERIFY(!m.isIdentity()); + } +} + +// Test isIdentity() for 4x4 matrices. +void tst_QMatrixNxN::isIdentity4x4() +{ + for (int i = 0; i < 4 * 4; ++i) { + QMatrix4x4 m; + QVERIFY(m.isIdentity()); + m.data()[i] = 42.0f; + QVERIFY(!m.isIdentity()); + } + + // Force the "Identity" flag bit to be lost and check again. + QMatrix4x4 m2; + m2.data()[0] = 1.0f; + QVERIFY(m2.isIdentity()); +} + +// Test isIdentity() for 4x3 matrices. +void tst_QMatrixNxN::isIdentity4x3() +{ + for (int i = 0; i < 4 * 3; ++i) { + QMatrix4x3 m; + QVERIFY(m.isIdentity()); + m.data()[i] = 42.0f; + QVERIFY(!m.isIdentity()); + } +} + +// Test 2x2 matrix comparisons. +void tst_QMatrixNxN::compare2x2() +{ + QMatrix2x2 m1(uniqueValues2); + QMatrix2x2 m2(uniqueValues2); + QMatrix2x2 m3(transposedValues2); + + QVERIFY(m1 == m2); + QVERIFY(!(m1 != m2)); + QVERIFY(m1 != m3); + QVERIFY(!(m1 == m3)); +} + +// Test 3x3 matrix comparisons. +void tst_QMatrixNxN::compare3x3() +{ + QMatrix3x3 m1(uniqueValues3); + QMatrix3x3 m2(uniqueValues3); + QMatrix3x3 m3(transposedValues3); + + QVERIFY(m1 == m2); + QVERIFY(!(m1 != m2)); + QVERIFY(m1 != m3); + QVERIFY(!(m1 == m3)); +} + +// Test 4x4 matrix comparisons. +void tst_QMatrixNxN::compare4x4() +{ + QMatrix4x4 m1(uniqueValues4); + QMatrix4x4 m2(uniqueValues4); + QMatrix4x4 m3(transposedValues4); + + QVERIFY(m1 == m2); + QVERIFY(!(m1 != m2)); + QVERIFY(m1 != m3); + QVERIFY(!(m1 == m3)); +} + +// Test 4x3 matrix comparisons. +void tst_QMatrixNxN::compare4x3() +{ + QMatrix4x3 m1(uniqueValues4x3); + QMatrix4x3 m2(uniqueValues4x3); + QMatrix4x3 m3(transposedValues3x4); + + QVERIFY(m1 == m2); + QVERIFY(!(m1 != m2)); + QVERIFY(m1 != m3); + QVERIFY(!(m1 == m3)); +} + +// Test matrix 2x2 transpose operations. +void tst_QMatrixNxN::transposed2x2() +{ + // Transposing the identity should result in the identity. + QMatrix2x2 m1; + QMatrix2x2 m2 = m1.transposed(); + QVERIFY(isIdentity(m2)); + + // Transpose a more interesting matrix that allows us to track + // exactly where each source element ends up. + QMatrix2x2 m3(uniqueValues2); + QMatrix2x2 m4 = m3.transposed(); + QVERIFY(isSame(m4, transposedValues2)); + + // Transpose in-place, just to check that the compiler is sane. + m3 = m3.transposed(); + QVERIFY(isSame(m3, transposedValues2)); +} + +// Test matrix 3x3 transpose operations. +void tst_QMatrixNxN::transposed3x3() +{ + // Transposing the identity should result in the identity. + QMatrix3x3 m1; + QMatrix3x3 m2 = m1.transposed(); + QVERIFY(isIdentity(m2)); + + // Transpose a more interesting matrix that allows us to track + // exactly where each source element ends up. + QMatrix3x3 m3(uniqueValues3); + QMatrix3x3 m4 = m3.transposed(); + QVERIFY(isSame(m4, transposedValues3)); + + // Transpose in-place, just to check that the compiler is sane. + m3 = m3.transposed(); + QVERIFY(isSame(m3, transposedValues3)); +} + +// Test matrix 4x4 transpose operations. +void tst_QMatrixNxN::transposed4x4() +{ + // Transposing the identity should result in the identity. + QMatrix4x4 m1; + QMatrix4x4 m2 = m1.transposed(); + QVERIFY(isIdentity(m2)); + + // Transpose a more interesting matrix that allows us to track + // exactly where each source element ends up. + QMatrix4x4 m3(uniqueValues4); + QMatrix4x4 m4 = m3.transposed(); + QVERIFY(isSame(m4, transposedValues4)); + + // Transpose in-place, just to check that the compiler is sane. + m3 = m3.transposed(); + QVERIFY(isSame(m3, transposedValues4)); +} + +// Test matrix 4x3 transpose operations. +void tst_QMatrixNxN::transposed4x3() +{ + QMatrix4x3 m3(uniqueValues4x3); + QMatrix3x4 m4 = m3.transposed(); + qreal values[12]; + m4.toValueArray(values); + for (int index = 0; index < 12; ++index) + QCOMPARE(values[index], transposedValues3x4[index]); +} + +// Test matrix addition for 2x2 matrices. +void tst_QMatrixNxN::add2x2_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<void *>("m2Values"); + QTest::addColumn<void *>("m3Values"); + + QTest::newRow("null") + << (void *)nullValues2 << (void *)nullValues2 << (void *)nullValues2; + + QTest::newRow("identity/null") + << (void *)identityValues2 << (void *)nullValues2 << (void *)identityValues2; + + QTest::newRow("identity/identity") + << (void *)identityValues2 << (void *)identityValues2 << (void *)doubleIdentity2; + + static qreal const sumValues[16] = + {2.0f, 7.0f, + 7.0f, 12.0f}; + QTest::newRow("unique") + << (void *)uniqueValues2 << (void *)transposedValues2 << (void *)sumValues; +} +void tst_QMatrixNxN::add2x2() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(void *, m3Values); + + QMatrix2x2 m1((const qreal *)m1Values); + QMatrix2x2 m2((const qreal *)m2Values); + + QMatrix2x2 m4(m1); + m4 += m2; + QVERIFY(isSame(m4, (const qreal *)m3Values)); + + QMatrix2x2 m5; + m5 = m1 + m2; + QVERIFY(isSame(m5, (const qreal *)m3Values)); +} + +// Test matrix addition for 3x3 matrices. +void tst_QMatrixNxN::add3x3_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<void *>("m2Values"); + QTest::addColumn<void *>("m3Values"); + + QTest::newRow("null") + << (void *)nullValues3 << (void *)nullValues3 << (void *)nullValues3; + + QTest::newRow("identity/null") + << (void *)identityValues3 << (void *)nullValues3 << (void *)identityValues3; + + QTest::newRow("identity/identity") + << (void *)identityValues3 << (void *)identityValues3 << (void *)doubleIdentity3; + + static qreal const sumValues[16] = + {2.0f, 7.0f, 12.0f, + 7.0f, 12.0f, 17.0f, + 12.0f, 17.0f, 22.0f}; + QTest::newRow("unique") + << (void *)uniqueValues3 << (void *)transposedValues3 << (void *)sumValues; +} +void tst_QMatrixNxN::add3x3() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(void *, m3Values); + + QMatrix3x3 m1((const qreal *)m1Values); + QMatrix3x3 m2((const qreal *)m2Values); + + QMatrix3x3 m4(m1); + m4 += m2; + QVERIFY(isSame(m4, (const qreal *)m3Values)); + + QMatrix3x3 m5; + m5 = m1 + m2; + QVERIFY(isSame(m5, (const qreal *)m3Values)); +} + +// Test matrix addition for 4x4 matrices. +void tst_QMatrixNxN::add4x4_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<void *>("m2Values"); + QTest::addColumn<void *>("m3Values"); + + QTest::newRow("null") + << (void *)nullValues4 << (void *)nullValues4 << (void *)nullValues4; + + QTest::newRow("identity/null") + << (void *)identityValues4 << (void *)nullValues4 << (void *)identityValues4; + + QTest::newRow("identity/identity") + << (void *)identityValues4 << (void *)identityValues4 << (void *)doubleIdentity4; + + static qreal const sumValues[16] = + {2.0f, 7.0f, 12.0f, 17.0f, + 7.0f, 12.0f, 17.0f, 22.0f, + 12.0f, 17.0f, 22.0f, 27.0f, + 17.0f, 22.0f, 27.0f, 32.0f}; + QTest::newRow("unique") + << (void *)uniqueValues4 << (void *)transposedValues4 << (void *)sumValues; +} +void tst_QMatrixNxN::add4x4() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(void *, m3Values); + + QMatrix4x4 m1((const qreal *)m1Values); + QMatrix4x4 m2((const qreal *)m2Values); + + QMatrix4x4 m4(m1); + m4 += m2; + QVERIFY(isSame(m4, (const qreal *)m3Values)); + + QMatrix4x4 m5; + m5 = m1 + m2; + QVERIFY(isSame(m5, (const qreal *)m3Values)); +} + +// Test matrix addition for 4x3 matrices. +void tst_QMatrixNxN::add4x3_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<void *>("m2Values"); + QTest::addColumn<void *>("m3Values"); + + QTest::newRow("null") + << (void *)nullValues4x3 << (void *)nullValues4x3 << (void *)nullValues4x3; + + QTest::newRow("identity/null") + << (void *)identityValues4x3 << (void *)nullValues4x3 << (void *)identityValues4x3; + + QTest::newRow("identity/identity") + << (void *)identityValues4x3 << (void *)identityValues4x3 << (void *)doubleIdentity4x3; + + static qreal const sumValues[16] = + {2.0f, 7.0f, 12.0f, 6.0f, + 11.0f, 16.0f, 10.0f, 15.0f, + 20.0f, 14.0f, 19.0f, 24.0f}; + QTest::newRow("unique") + << (void *)uniqueValues4x3 << (void *)transposedValues3x4 << (void *)sumValues; +} +void tst_QMatrixNxN::add4x3() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(void *, m3Values); + + QMatrix4x3 m1((const qreal *)m1Values); + QMatrix4x3 m2((const qreal *)m2Values); + + QMatrix4x3 m4(m1); + m4 += m2; + QVERIFY(isSame(m4, (const qreal *)m3Values)); + + QMatrix4x3 m5; + m5 = m1 + m2; + QVERIFY(isSame(m5, (const qreal *)m3Values)); +} + +// Test matrix subtraction for 2x2 matrices. +void tst_QMatrixNxN::subtract2x2_data() +{ + // Use the same test cases as the add test. + add2x2_data(); +} +void tst_QMatrixNxN::subtract2x2() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(void *, m3Values); + + QMatrix2x2 m1((const qreal *)m1Values); + QMatrix2x2 m2((const qreal *)m2Values); + QMatrix2x2 m3((const qreal *)m3Values); + + QMatrix2x2 m4(m3); + m4 -= m1; + QVERIFY(isSame(m4, (const qreal *)m2Values)); + + QMatrix2x2 m5; + m5 = m3 - m1; + QVERIFY(isSame(m5, (const qreal *)m2Values)); + + QMatrix2x2 m6(m3); + m6 -= m2; + QVERIFY(isSame(m6, (const qreal *)m1Values)); + + QMatrix2x2 m7; + m7 = m3 - m2; + QVERIFY(isSame(m7, (const qreal *)m1Values)); +} + +// Test matrix subtraction for 3x3 matrices. +void tst_QMatrixNxN::subtract3x3_data() +{ + // Use the same test cases as the add test. + add3x3_data(); +} +void tst_QMatrixNxN::subtract3x3() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(void *, m3Values); + + QMatrix3x3 m1((const qreal *)m1Values); + QMatrix3x3 m2((const qreal *)m2Values); + QMatrix3x3 m3((const qreal *)m3Values); + + QMatrix3x3 m4(m3); + m4 -= m1; + QVERIFY(isSame(m4, (const qreal *)m2Values)); + + QMatrix3x3 m5; + m5 = m3 - m1; + QVERIFY(isSame(m5, (const qreal *)m2Values)); + + QMatrix3x3 m6(m3); + m6 -= m2; + QVERIFY(isSame(m6, (const qreal *)m1Values)); + + QMatrix3x3 m7; + m7 = m3 - m2; + QVERIFY(isSame(m7, (const qreal *)m1Values)); +} + +// Test matrix subtraction for 4x4 matrices. +void tst_QMatrixNxN::subtract4x4_data() +{ + // Use the same test cases as the add test. + add4x4_data(); +} +void tst_QMatrixNxN::subtract4x4() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(void *, m3Values); + + QMatrix4x4 m1((const qreal *)m1Values); + QMatrix4x4 m2((const qreal *)m2Values); + QMatrix4x4 m3((const qreal *)m3Values); + + QMatrix4x4 m4(m3); + m4 -= m1; + QVERIFY(isSame(m4, (const qreal *)m2Values)); + + QMatrix4x4 m5; + m5 = m3 - m1; + QVERIFY(isSame(m5, (const qreal *)m2Values)); + + QMatrix4x4 m6(m3); + m6 -= m2; + QVERIFY(isSame(m6, (const qreal *)m1Values)); + + QMatrix4x4 m7; + m7 = m3 - m2; + QVERIFY(isSame(m7, (const qreal *)m1Values)); +} + +// Test matrix subtraction for 4x3 matrices. +void tst_QMatrixNxN::subtract4x3_data() +{ + // Use the same test cases as the add test. + add4x3_data(); +} +void tst_QMatrixNxN::subtract4x3() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(void *, m3Values); + + QMatrix4x3 m1((const qreal *)m1Values); + QMatrix4x3 m2((const qreal *)m2Values); + QMatrix4x3 m3((const qreal *)m3Values); + + QMatrix4x3 m4(m3); + m4 -= m1; + QVERIFY(isSame(m4, (const qreal *)m2Values)); + + QMatrix4x3 m5; + m5 = m3 - m1; + QVERIFY(isSame(m5, (const qreal *)m2Values)); + + QMatrix4x3 m6(m3); + m6 -= m2; + QVERIFY(isSame(m6, (const qreal *)m1Values)); + + QMatrix4x3 m7; + m7 = m3 - m2; + QVERIFY(isSame(m7, (const qreal *)m1Values)); +} + +// Test matrix multiplication for 2x2 matrices. +void tst_QMatrixNxN::multiply2x2_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<void *>("m2Values"); + QTest::addColumn<void *>("m3Values"); + + QTest::newRow("null") + << (void *)nullValues2 << (void *)nullValues2 << (void *)nullValues2; + + QTest::newRow("null/unique") + << (void *)nullValues2 << (void *)uniqueValues2 << (void *)nullValues2; + + QTest::newRow("unique/null") + << (void *)uniqueValues2 << (void *)nullValues2 << (void *)nullValues2; + + QTest::newRow("unique/identity") + << (void *)uniqueValues2 << (void *)identityValues2 << (void *)uniqueValues2; + + QTest::newRow("identity/unique") + << (void *)identityValues2 << (void *)uniqueValues2 << (void *)uniqueValues2; + + static qreal uniqueResult[4]; + for (int row = 0; row < 2; ++row) { + for (int col = 0; col < 2; ++col) { + qreal sum = 0.0f; + for (int j = 0; j < 2; ++j) + sum += uniqueValues2[row * 2 + j] * transposedValues2[j * 2 + col]; + uniqueResult[row * 2 + col] = sum; + } + } + + QTest::newRow("unique/transposed") + << (void *)uniqueValues2 << (void *)transposedValues2 << (void *)uniqueResult; +} +void tst_QMatrixNxN::multiply2x2() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(void *, m3Values); + + QMatrix2x2 m1((const qreal *)m1Values); + QMatrix2x2 m2((const qreal *)m2Values); + + QMatrix2x2 m5; + m5 = m1 * m2; + QVERIFY(isSame(m5, (const qreal *)m3Values)); +} + +// Test matrix multiplication for 3x3 matrices. +void tst_QMatrixNxN::multiply3x3_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<void *>("m2Values"); + QTest::addColumn<void *>("m3Values"); + + QTest::newRow("null") + << (void *)nullValues3 << (void *)nullValues3 << (void *)nullValues3; + + QTest::newRow("null/unique") + << (void *)nullValues3 << (void *)uniqueValues3 << (void *)nullValues3; + + QTest::newRow("unique/null") + << (void *)uniqueValues3 << (void *)nullValues3 << (void *)nullValues3; + + QTest::newRow("unique/identity") + << (void *)uniqueValues3 << (void *)identityValues3 << (void *)uniqueValues3; + + QTest::newRow("identity/unique") + << (void *)identityValues3 << (void *)uniqueValues3 << (void *)uniqueValues3; + + static qreal uniqueResult[9]; + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) { + qreal sum = 0.0f; + for (int j = 0; j < 3; ++j) + sum += uniqueValues3[row * 3 + j] * transposedValues3[j * 3 + col]; + uniqueResult[row * 3 + col] = sum; + } + } + + QTest::newRow("unique/transposed") + << (void *)uniqueValues3 << (void *)transposedValues3 << (void *)uniqueResult; +} +void tst_QMatrixNxN::multiply3x3() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(void *, m3Values); + + QMatrix3x3 m1((const qreal *)m1Values); + QMatrix3x3 m2((const qreal *)m2Values); + + QMatrix3x3 m5; + m5 = m1 * m2; + QVERIFY(isSame(m5, (const qreal *)m3Values)); +} + +// Test matrix multiplication for 4x4 matrices. +void tst_QMatrixNxN::multiply4x4_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<void *>("m2Values"); + QTest::addColumn<void *>("m3Values"); + + QTest::newRow("null") + << (void *)nullValues4 << (void *)nullValues4 << (void *)nullValues4; + + QTest::newRow("null/unique") + << (void *)nullValues4 << (void *)uniqueValues4 << (void *)nullValues4; + + QTest::newRow("unique/null") + << (void *)uniqueValues4 << (void *)nullValues4 << (void *)nullValues4; + + QTest::newRow("unique/identity") + << (void *)uniqueValues4 << (void *)identityValues4 << (void *)uniqueValues4; + + QTest::newRow("identity/unique") + << (void *)identityValues4 << (void *)uniqueValues4 << (void *)uniqueValues4; + + static qreal uniqueResult[16]; + for (int row = 0; row < 4; ++row) { + for (int col = 0; col < 4; ++col) { + qreal sum = 0.0f; + for (int j = 0; j < 4; ++j) + sum += uniqueValues4[row * 4 + j] * transposedValues4[j * 4 + col]; + uniqueResult[row * 4 + col] = sum; + } + } + + QTest::newRow("unique/transposed") + << (void *)uniqueValues4 << (void *)transposedValues4 << (void *)uniqueResult; +} +void tst_QMatrixNxN::multiply4x4() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(void *, m3Values); + + QMatrix4x4 m1((const qreal *)m1Values); + QMatrix4x4 m2((const qreal *)m2Values); + + QMatrix4x4 m4; + m4 = m1; + m4 *= m2; + QVERIFY(isSame(m4, (const qreal *)m3Values)); + + QMatrix4x4 m5; + m5 = m1 * m2; + QVERIFY(isSame(m5, (const qreal *)m3Values)); +} + +// Test matrix multiplication for 4x3 matrices. +void tst_QMatrixNxN::multiply4x3_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<void *>("m2Values"); + QTest::addColumn<void *>("m3Values"); + + QTest::newRow("null") + << (void *)nullValues4x3 << (void *)nullValues4x3 << (void *)nullValues3; + + QTest::newRow("null/unique") + << (void *)nullValues4x3 << (void *)uniqueValues4x3 << (void *)nullValues3; + + QTest::newRow("unique/null") + << (void *)uniqueValues4x3 << (void *)nullValues4x3 << (void *)nullValues3; + + static qreal uniqueResult[9]; + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) { + qreal sum = 0.0f; + for (int j = 0; j < 4; ++j) + sum += uniqueValues4x3[row * 4 + j] * transposedValues3x4[j * 3 + col]; + uniqueResult[row * 3 + col] = sum; + } + } + + QTest::newRow("unique/transposed") + << (void *)uniqueValues4x3 << (void *)transposedValues3x4 << (void *)uniqueResult; +} +void tst_QMatrixNxN::multiply4x3() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(void *, m3Values); + + QMatrix4x3 m1((const qreal *)m1Values); + QMatrix3x4 m2((const qreal *)m2Values); + + QGenericMatrix<3, 3, qreal, float> m4; + m4 = m1 * m2; + qreal values[9]; + m4.toValueArray(values); + for (int index = 0; index < 9; ++index) + QCOMPARE(values[index], ((const qreal *)m3Values)[index]); +} + +// Test matrix multiplication by a factor for 2x2 matrices. +void tst_QMatrixNxN::multiplyFactor2x2_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<qreal>("factor"); + QTest::addColumn<void *>("m2Values"); + + QTest::newRow("null") + << (void *)nullValues2 << (qreal)1.0f << (void *)nullValues2; + + QTest::newRow("double identity") + << (void *)identityValues2 << (qreal)2.0f << (void *)doubleIdentity2; + + static qreal const values[16] = + {1.0f, 2.0f, + 5.0f, 6.0f}; + static qreal const doubleValues[16] = + {2.0f, 4.0f, + 10.0f, 12.0f}; + static qreal const negDoubleValues[16] = + {-2.0f, -4.0f, + -10.0f, -12.0f}; + + QTest::newRow("unique") + << (void *)values << (qreal)2.0f << (void *)doubleValues; + + QTest::newRow("neg") + << (void *)values << (qreal)-2.0f << (void *)negDoubleValues; + + QTest::newRow("zero") + << (void *)values << (qreal)0.0f << (void *)nullValues4; +} +void tst_QMatrixNxN::multiplyFactor2x2() +{ + QFETCH(void *, m1Values); + QFETCH(qreal, factor); + QFETCH(void *, m2Values); + + QMatrix2x2 m1((const qreal *)m1Values); + + QMatrix2x2 m3; + m3 = m1; + m3 *= factor; + QVERIFY(isSame(m3, (const qreal *)m2Values)); + + QMatrix2x2 m4; + m4 = m1 * factor; + QVERIFY(isSame(m4, (const qreal *)m2Values)); + + QMatrix2x2 m5; + m5 = factor * m1; + QVERIFY(isSame(m5, (const qreal *)m2Values)); +} + +// Test matrix multiplication by a factor for 3x3 matrices. +void tst_QMatrixNxN::multiplyFactor3x3_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<qreal>("factor"); + QTest::addColumn<void *>("m2Values"); + + QTest::newRow("null") + << (void *)nullValues3 << (qreal)1.0f << (void *)nullValues3; + + QTest::newRow("double identity") + << (void *)identityValues3 << (qreal)2.0f << (void *)doubleIdentity3; + + static qreal const values[16] = + {1.0f, 2.0f, 3.0f, + 5.0f, 6.0f, 7.0f, + 9.0f, 10.0f, 11.0f}; + static qreal const doubleValues[16] = + {2.0f, 4.0f, 6.0f, + 10.0f, 12.0f, 14.0f, + 18.0f, 20.0f, 22.0f}; + static qreal const negDoubleValues[16] = + {-2.0f, -4.0f, -6.0f, + -10.0f, -12.0f, -14.0f, + -18.0f, -20.0f, -22.0f}; + + QTest::newRow("unique") + << (void *)values << (qreal)2.0f << (void *)doubleValues; + + QTest::newRow("neg") + << (void *)values << (qreal)-2.0f << (void *)negDoubleValues; + + QTest::newRow("zero") + << (void *)values << (qreal)0.0f << (void *)nullValues4; +} +void tst_QMatrixNxN::multiplyFactor3x3() +{ + QFETCH(void *, m1Values); + QFETCH(qreal, factor); + QFETCH(void *, m2Values); + + QMatrix3x3 m1((const qreal *)m1Values); + + QMatrix3x3 m3; + m3 = m1; + m3 *= factor; + QVERIFY(isSame(m3, (const qreal *)m2Values)); + + QMatrix3x3 m4; + m4 = m1 * factor; + QVERIFY(isSame(m4, (const qreal *)m2Values)); + + QMatrix3x3 m5; + m5 = factor * m1; + QVERIFY(isSame(m5, (const qreal *)m2Values)); +} + +// Test matrix multiplication by a factor for 4x4 matrices. +void tst_QMatrixNxN::multiplyFactor4x4_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<qreal>("factor"); + QTest::addColumn<void *>("m2Values"); + + QTest::newRow("null") + << (void *)nullValues4 << (qreal)1.0f << (void *)nullValues4; + + QTest::newRow("double identity") + << (void *)identityValues4 << (qreal)2.0f << (void *)doubleIdentity4; + + static qreal const values[16] = + {1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f}; + static qreal const doubleValues[16] = + {2.0f, 4.0f, 6.0f, 8.0f, + 10.0f, 12.0f, 14.0f, 16.0f, + 18.0f, 20.0f, 22.0f, 24.0f, + 26.0f, 28.0f, 30.0f, 32.0f}; + static qreal const negDoubleValues[16] = + {-2.0f, -4.0f, -6.0f, -8.0f, + -10.0f, -12.0f, -14.0f, -16.0f, + -18.0f, -20.0f, -22.0f, -24.0f, + -26.0f, -28.0f, -30.0f, -32.0f}; + + QTest::newRow("unique") + << (void *)values << (qreal)2.0f << (void *)doubleValues; + + QTest::newRow("neg") + << (void *)values << (qreal)-2.0f << (void *)negDoubleValues; + + QTest::newRow("zero") + << (void *)values << (qreal)0.0f << (void *)nullValues4; +} +void tst_QMatrixNxN::multiplyFactor4x4() +{ + QFETCH(void *, m1Values); + QFETCH(qreal, factor); + QFETCH(void *, m2Values); + + QMatrix4x4 m1((const qreal *)m1Values); + + QMatrix4x4 m3; + m3 = m1; + m3 *= factor; + QVERIFY(isSame(m3, (const qreal *)m2Values)); + + QMatrix4x4 m4; + m4 = m1 * factor; + QVERIFY(isSame(m4, (const qreal *)m2Values)); + + QMatrix4x4 m5; + m5 = factor * m1; + QVERIFY(isSame(m5, (const qreal *)m2Values)); +} + +// Test matrix multiplication by a factor for 4x3 matrices. +void tst_QMatrixNxN::multiplyFactor4x3_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<qreal>("factor"); + QTest::addColumn<void *>("m2Values"); + + QTest::newRow("null") + << (void *)nullValues4x3 << (qreal)1.0f << (void *)nullValues4x3; + + QTest::newRow("double identity") + << (void *)identityValues4x3 << (qreal)2.0f << (void *)doubleIdentity4x3; + + static qreal const values[12] = + {1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f}; + static qreal const doubleValues[12] = + {2.0f, 4.0f, 6.0f, 8.0f, + 10.0f, 12.0f, 14.0f, 16.0f, + 18.0f, 20.0f, 22.0f, 24.0f}; + static qreal const negDoubleValues[12] = + {-2.0f, -4.0f, -6.0f, -8.0f, + -10.0f, -12.0f, -14.0f, -16.0f, + -18.0f, -20.0f, -22.0f, -24.0f}; + + QTest::newRow("unique") + << (void *)values << (qreal)2.0f << (void *)doubleValues; + + QTest::newRow("neg") + << (void *)values << (qreal)-2.0f << (void *)negDoubleValues; + + QTest::newRow("zero") + << (void *)values << (qreal)0.0f << (void *)nullValues4x3; +} +void tst_QMatrixNxN::multiplyFactor4x3() +{ + QFETCH(void *, m1Values); + QFETCH(qreal, factor); + QFETCH(void *, m2Values); + + QMatrix4x3 m1((const qreal *)m1Values); + + QMatrix4x3 m3; + m3 = m1; + m3 *= factor; + QVERIFY(isSame(m3, (const qreal *)m2Values)); + + QMatrix4x3 m4; + m4 = m1 * factor; + QVERIFY(isSame(m4, (const qreal *)m2Values)); + + QMatrix4x3 m5; + m5 = factor * m1; + QVERIFY(isSame(m5, (const qreal *)m2Values)); +} + +// Test matrix division by a factor for 2x2 matrices. +void tst_QMatrixNxN::divideFactor2x2_data() +{ + // Use the same test cases as the multiplyFactor test. + multiplyFactor2x2_data(); +} +void tst_QMatrixNxN::divideFactor2x2() +{ + QFETCH(void *, m1Values); + QFETCH(qreal, factor); + QFETCH(void *, m2Values); + + if (factor == 0.0f) + return; + + QMatrix2x2 m2((const qreal *)m2Values); + + QMatrix2x2 m3; + m3 = m2; + m3 /= factor; + QVERIFY(isSame(m3, (const qreal *)m1Values)); + + QMatrix2x2 m4; + m4 = m2 / factor; + QVERIFY(isSame(m4, (const qreal *)m1Values)); +} + +// Test matrix division by a factor for 3x3 matrices. +void tst_QMatrixNxN::divideFactor3x3_data() +{ + // Use the same test cases as the multiplyFactor test. + multiplyFactor3x3_data(); +} +void tst_QMatrixNxN::divideFactor3x3() +{ + QFETCH(void *, m1Values); + QFETCH(qreal, factor); + QFETCH(void *, m2Values); + + if (factor == 0.0f) + return; + + QMatrix3x3 m2((const qreal *)m2Values); + + QMatrix3x3 m3; + m3 = m2; + m3 /= factor; + QVERIFY(isSame(m3, (const qreal *)m1Values)); + + QMatrix3x3 m4; + m4 = m2 / factor; + QVERIFY(isSame(m4, (const qreal *)m1Values)); +} + +// Test matrix division by a factor for 4x4 matrices. +void tst_QMatrixNxN::divideFactor4x4_data() +{ + // Use the same test cases as the multiplyFactor test. + multiplyFactor4x4_data(); +} +void tst_QMatrixNxN::divideFactor4x4() +{ + QFETCH(void *, m1Values); + QFETCH(qreal, factor); + QFETCH(void *, m2Values); + + if (factor == 0.0f) + return; + + QMatrix4x4 m2((const qreal *)m2Values); + + QMatrix4x4 m3; + m3 = m2; + m3 /= factor; + QVERIFY(isSame(m3, (const qreal *)m1Values)); + + QMatrix4x4 m4; + m4 = m2 / factor; + QVERIFY(isSame(m4, (const qreal *)m1Values)); +} + +// Test matrix division by a factor for 4x3 matrices. +void tst_QMatrixNxN::divideFactor4x3_data() +{ + // Use the same test cases as the multiplyFactor test. + multiplyFactor4x3_data(); +} +void tst_QMatrixNxN::divideFactor4x3() +{ + QFETCH(void *, m1Values); + QFETCH(qreal, factor); + QFETCH(void *, m2Values); + + if (factor == 0.0f) + return; + + QMatrix4x3 m2((const qreal *)m2Values); + + QMatrix4x3 m3; + m3 = m2; + m3 /= factor; + QVERIFY(isSame(m3, (const qreal *)m1Values)); + + QMatrix4x3 m4; + m4 = m2 / factor; + QVERIFY(isSame(m4, (const qreal *)m1Values)); +} + +// Test matrix negation for 2x2 matrices. +void tst_QMatrixNxN::negate2x2_data() +{ + // Use the same test cases as the multiplyFactor test. + multiplyFactor2x2_data(); +} +void tst_QMatrixNxN::negate2x2() +{ + QFETCH(void *, m1Values); + + const qreal *values = (const qreal *)m1Values; + + QMatrix2x2 m1(values); + + qreal negated[4]; + for (int index = 0; index < 4; ++index) + negated[index] = -values[index]; + + QMatrix2x2 m2; + m2 = -m1; + QVERIFY(isSame(m2, negated)); +} + +// Test matrix negation for 3x3 matrices. +void tst_QMatrixNxN::negate3x3_data() +{ + // Use the same test cases as the multiplyFactor test. + multiplyFactor3x3_data(); +} +void tst_QMatrixNxN::negate3x3() +{ + QFETCH(void *, m1Values); + + const qreal *values = (const qreal *)m1Values; + + QMatrix3x3 m1(values); + + qreal negated[9]; + for (int index = 0; index < 9; ++index) + negated[index] = -values[index]; + + QMatrix3x3 m2; + m2 = -m1; + QVERIFY(isSame(m2, negated)); +} + +// Test matrix negation for 4x4 matrices. +void tst_QMatrixNxN::negate4x4_data() +{ + // Use the same test cases as the multiplyFactor test. + multiplyFactor4x4_data(); +} +void tst_QMatrixNxN::negate4x4() +{ + QFETCH(void *, m1Values); + + const qreal *values = (const qreal *)m1Values; + + QMatrix4x4 m1(values); + + qreal negated[16]; + for (int index = 0; index < 16; ++index) + negated[index] = -values[index]; + + QMatrix4x4 m2; + m2 = -m1; + QVERIFY(isSame(m2, negated)); +} + +// Test matrix negation for 4x3 matrices. +void tst_QMatrixNxN::negate4x3_data() +{ + // Use the same test cases as the multiplyFactor test. + multiplyFactor4x3_data(); +} +void tst_QMatrixNxN::negate4x3() +{ + QFETCH(void *, m1Values); + + const qreal *values = (const qreal *)m1Values; + + QMatrix4x3 m1(values); + + qreal negated[12]; + for (int index = 0; index < 12; ++index) + negated[index] = -values[index]; + + QMatrix4x3 m2; + m2 = -m1; + QVERIFY(isSame(m2, negated)); +} + +// Matrix inverted. This is a more straight-forward implementation +// of the algorithm at http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q24 +// than the optimized version in the QMatrix4x4 code. Hopefully it is +// easier to verify that this version is the same as the reference. + +struct Matrix3 +{ + qreal v[9]; +}; +struct Matrix4 +{ + qreal v[16]; +}; + +static qreal m3Determinant(const Matrix3& m) +{ + return m.v[0] * (m.v[4] * m.v[8] - m.v[7] * m.v[5]) - + m.v[1] * (m.v[3] * m.v[8] - m.v[6] * m.v[5]) + + m.v[2] * (m.v[3] * m.v[7] - m.v[6] * m.v[4]); +} + +static bool m3Inverse(const Matrix3& min, Matrix3& mout) +{ + qreal det = m3Determinant(min); + if (det == 0.0f) + return false; + mout.v[0] = (min.v[4] * min.v[8] - min.v[5] * min.v[7]) / det; + mout.v[1] = -(min.v[1] * min.v[8] - min.v[2] * min.v[7]) / det; + mout.v[2] = (min.v[1] * min.v[5] - min.v[4] * min.v[2]) / det; + mout.v[3] = -(min.v[3] * min.v[8] - min.v[5] * min.v[6]) / det; + mout.v[4] = (min.v[0] * min.v[8] - min.v[6] * min.v[2]) / det; + mout.v[5] = -(min.v[0] * min.v[5] - min.v[3] * min.v[2]) / det; + mout.v[6] = (min.v[3] * min.v[7] - min.v[6] * min.v[4]) / det; + mout.v[7] = -(min.v[0] * min.v[7] - min.v[6] * min.v[1]) / det; + mout.v[8] = (min.v[0] * min.v[4] - min.v[1] * min.v[3]) / det; + return true; +} + +static void m3Transpose(Matrix3& m) +{ + qSwap(m.v[1], m.v[3]); + qSwap(m.v[2], m.v[6]); + qSwap(m.v[5], m.v[7]); +} + +static void m4Submatrix(const Matrix4& min, Matrix3& mout, int i, int j) +{ + for (int di = 0; di < 3; ++di) { + for (int dj = 0; dj < 3; ++dj) { + int si = di + ((di >= i) ? 1 : 0); + int sj = dj + ((dj >= j) ? 1 : 0); + mout.v[di * 3 + dj] = min.v[si * 4 + sj]; + } + } +} + +static qreal m4Determinant(const Matrix4& m) +{ + qreal det; + qreal result = 0.0f; + qreal i = 1.0f; + Matrix3 msub; + for (int n = 0; n < 4; ++n, i *= -1.0f) { + m4Submatrix(m, msub, 0, n); + det = m3Determinant(msub); + result += m.v[n] * det * i; + } + return result; +} + +static void m4Inverse(const Matrix4& min, Matrix4& mout) +{ + qreal det = m4Determinant(min); + Matrix3 msub; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + qreal sign = 1.0f - ((i + j) % 2) * 2.0f; + m4Submatrix(min, msub, i, j); + mout.v[i + j * 4] = (m3Determinant(msub) * sign) / det; + } + } +} + +// Test matrix inverted for 4x4 matrices. +void tst_QMatrixNxN::inverted4x4_data() +{ + QTest::addColumn<void *>("m1Values"); + QTest::addColumn<void *>("m2Values"); + QTest::addColumn<bool>("invertible"); + + QTest::newRow("null") + << (void *)nullValues4 << (void *)identityValues4 << false; + + QTest::newRow("identity") + << (void *)identityValues4 << (void *)identityValues4 << true; + + QTest::newRow("unique") + << (void *)uniqueValues4 << (void *)identityValues4 << false; + + static Matrix4 const invertible = { + {5.0f, 0.0f, 0.0f, 2.0f, + 0.0f, 6.0f, 0.0f, 3.0f, + 0.0f, 0.0f, 7.0f, 4.0f, + 0.0f, 0.0f, 0.0f, 1.0f} + }; + static Matrix4 inverted; + m4Inverse(invertible, inverted); + + QTest::newRow("invertible") + << (void *)invertible.v << (void *)inverted.v << true; + + static Matrix4 const translate = { + {1.0f, 0.0f, 0.0f, 2.0f, + 0.0f, 1.0f, 0.0f, 3.0f, + 0.0f, 0.0f, 1.0f, 4.0f, + 0.0f, 0.0f, 0.0f, 1.0f} + }; + static Matrix4 const inverseTranslate = { + {1.0f, 0.0f, 0.0f, -2.0f, + 0.0f, 1.0f, 0.0f, -3.0f, + 0.0f, 0.0f, 1.0f, -4.0f, + 0.0f, 0.0f, 0.0f, 1.0f} + }; + + QTest::newRow("translate") + << (void *)translate.v << (void *)inverseTranslate.v << true; +} +void tst_QMatrixNxN::inverted4x4() +{ + QFETCH(void *, m1Values); + QFETCH(void *, m2Values); + QFETCH(bool, invertible); + + QMatrix4x4 m1((const qreal *)m1Values); + + if (invertible) + QVERIFY(m1.determinant() != 0.0f); + else + QVERIFY(m1.determinant() == 0.0f); + + Matrix4 m1alt; + memcpy(m1alt.v, (const qreal *)m1Values, sizeof(m1alt.v)); + + QCOMPARE((float)(m1.determinant()), (float)(m4Determinant(m1alt))); + + QMatrix4x4 m2; + bool inv; + m2 = m1.inverted(&inv); + QVERIFY(isSame(m2, (const qreal *)m2Values)); + + if (invertible) { + QVERIFY(inv); + + Matrix4 m2alt; + m4Inverse(m1alt, m2alt); + QVERIFY(isSame(m2, m2alt.v)); + + QMatrix4x4 m3; + m3 = m1 * m2; + QVERIFY(isIdentity(m3)); + + QMatrix4x4 m4; + m4 = m2 * m1; + QVERIFY(isIdentity(m4)); + } else { + QVERIFY(!inv); + } + + // Test again, after inferring the special matrix type. + m1.inferSpecialType(); + m2 = m1.inverted(&inv); + QVERIFY(isSame(m2, (const qreal *)m2Values)); + QCOMPARE(inv, invertible); +} + +void tst_QMatrixNxN::orthonormalInverse4x4() +{ + QMatrix4x4 m1; + QVERIFY(matrixFuzzyCompare(m1.inverted(), m1)); + + QMatrix4x4 m2; + m2.rotate(45.0, 1.0, 0.0, 0.0); + m2.translate(10.0, 0.0, 0.0); + + // Use inferSpecialType() 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.inferSpecialType(); + bool invertible; + QVERIFY(matrixFuzzyCompare(m2.inverted(&invertible), m3.inverted())); + QVERIFY(invertible); + + QMatrix4x4 m4; + m4.rotate(45.0, 0.0, 1.0, 0.0); + QMatrix4x4 m5 = m4; + m5.inferSpecialType(); + QVERIFY(matrixFuzzyCompare(m4.inverted(), m5.inverted())); + + QMatrix4x4 m6; + m1.rotate(88, 0.0, 0.0, 1.0); + m1.translate(-20.0, 20.0, 15.0); + m1.rotate(25, 1.0, 0.0, 0.0); + QMatrix4x4 m7 = m6; + m7.inferSpecialType(); + QVERIFY(matrixFuzzyCompare(m6.inverted(), m7.inverted())); +} + +// Test the generation and use of 4x4 scale matrices. +void tst_QMatrixNxN::scale4x4_data() +{ + QTest::addColumn<qreal>("x"); + QTest::addColumn<qreal>("y"); + QTest::addColumn<qreal>("z"); + QTest::addColumn<void *>("resultValues"); + + static const qreal nullScale[] = + {0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("null") + << (qreal)0.0f << (qreal)0.0f << (qreal)0.0f << (void *)nullScale; + + QTest::newRow("identity") + << (qreal)1.0f << (qreal)1.0f << (qreal)1.0f << (void *)identityValues4; + + static const qreal doubleScale[] = + {2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 2.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("double") + << (qreal)2.0f << (qreal)2.0f << (qreal)2.0f << (void *)doubleScale; + + static const qreal complexScale[] = + {2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 11.0f, 0.0f, 0.0f, + 0.0f, 0.0f, -6.5f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("complex") + << (qreal)2.0f << (qreal)11.0f << (qreal)-6.5f << (void *)complexScale; + + static const qreal complexScale2D[] = + {2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, -11.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("complex2D") + << (qreal)2.0f << (qreal)-11.0f << (qreal)1.0f << (void *)complexScale2D; +} +void tst_QMatrixNxN::scale4x4() +{ + QFETCH(qreal, x); + QFETCH(qreal, y); + QFETCH(qreal, z); + QFETCH(void *, resultValues); + + QMatrix4x4 result((const qreal *)resultValues); + + QMatrix4x4 m1; + m1.scale(QVector3D(x, y, z)); + QVERIFY(isSame(m1, (const qreal *)resultValues)); + + QMatrix4x4 m2; + m2.scale(x, y, z); + QVERIFY(isSame(m2, (const qreal *)resultValues)); + + if (z == 1.0f) { + QMatrix4x4 m2b; + m2b.scale(x, y); + QVERIFY(m2b == m2); + } + + QVector3D v1(2.0f, 3.0f, -4.0f); + QVector3D v2 = m1 * v1; + QCOMPARE(v2.x(), (qreal)(2.0f * x)); + QCOMPARE(v2.y(), (qreal)(3.0f * y)); + QCOMPARE(v2.z(), (qreal)(-4.0f * z)); + + v2 = v1 * m1; + QCOMPARE(v2.x(), (qreal)(2.0f * x)); + QCOMPARE(v2.y(), (qreal)(3.0f * y)); + QCOMPARE(v2.z(), (qreal)(-4.0f * z)); + + QVector4D v3(2.0f, 3.0f, -4.0f, 34.0f); + QVector4D v4 = m1 * v3; + QCOMPARE(v4.x(), (qreal)(2.0f * x)); + QCOMPARE(v4.y(), (qreal)(3.0f * y)); + QCOMPARE(v4.z(), (qreal)(-4.0f * z)); + QCOMPARE(v4.w(), (qreal)34.0f); + + v4 = v3 * m1; + QCOMPARE(v4.x(), (qreal)(2.0f * x)); + QCOMPARE(v4.y(), (qreal)(3.0f * y)); + QCOMPARE(v4.z(), (qreal)(-4.0f * z)); + QCOMPARE(v4.w(), (qreal)34.0f); + + QPoint p1(2, 3); + QPoint p2 = m1 * p1; + QCOMPARE(p2.x(), (int)(2.0f * x)); + QCOMPARE(p2.y(), (int)(3.0f * y)); + + p2 = p1 * m1; + QCOMPARE(p2.x(), (int)(2.0f * x)); + QCOMPARE(p2.y(), (int)(3.0f * y)); + + QPointF p3(2.0f, 3.0f); + QPointF p4 = m1 * p3; + QCOMPARE(p4.x(), (qreal)(2.0f * x)); + QCOMPARE(p4.y(), (qreal)(3.0f * y)); + + p4 = p3 * m1; + QCOMPARE(p4.x(), (qreal)(2.0f * x)); + QCOMPARE(p4.y(), (qreal)(3.0f * y)); + + QMatrix4x4 m3(uniqueValues4); + QMatrix4x4 m4(m3); + m4.scale(x, y, z); + QVERIFY(m4 == m3 * m1); + + if (x == y && y == z) { + QMatrix4x4 m5; + m5.scale(x); + QVERIFY(isSame(m5, (const qreal *)resultValues)); + } + + if (z == 1.0f) { + QMatrix4x4 m4b(m3); + m4b.scale(x, y); + QVERIFY(m4b == m4); + } + + // Test coverage when the special matrix type is unknown. + + QMatrix4x4 m6; + m6(0, 0) = 1.0f; + m6.scale(QVector3D(x, y, z)); + QVERIFY(isSame(m6, (const qreal *)resultValues)); + + QMatrix4x4 m7; + m7(0, 0) = 1.0f; + m7.scale(x, y, z); + QVERIFY(isSame(m7, (const qreal *)resultValues)); + + if (x == y && y == z) { + QMatrix4x4 m8; + m8(0, 0) = 1.0f; + m8.scale(x); + QVERIFY(isSame(m8, (const qreal *)resultValues)); + + m8.inferSpecialType(); + m8.scale(1.0f); + QVERIFY(isSame(m8, (const qreal *)resultValues)); + + QMatrix4x4 m9; + m9.translate(0.0f, 0.0f, 0.0f); + m9.scale(x); + QVERIFY(isSame(m9, (const qreal *)resultValues)); + } +} + +// Test the generation and use of 4x4 translation matrices. +void tst_QMatrixNxN::translate4x4_data() +{ + QTest::addColumn<qreal>("x"); + QTest::addColumn<qreal>("y"); + QTest::addColumn<qreal>("z"); + QTest::addColumn<void *>("resultValues"); + + QTest::newRow("null") + << (qreal)0.0f << (qreal)0.0f << (qreal)0.0f << (void *)identityValues4; + + static const qreal identityTranslate[] = + {1.0f, 0.0f, 0.0f, 1.0f, + 0.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("identity") + << (qreal)1.0f << (qreal)1.0f << (qreal)1.0f << (void *)identityTranslate; + + static const qreal complexTranslate[] = + {1.0f, 0.0f, 0.0f, 2.0f, + 0.0f, 1.0f, 0.0f, 11.0f, + 0.0f, 0.0f, 1.0f, -6.5f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("complex") + << (qreal)2.0f << (qreal)11.0f << (qreal)-6.5f << (void *)complexTranslate; + + static const qreal complexTranslate2D[] = + {1.0f, 0.0f, 0.0f, 2.0f, + 0.0f, 1.0f, 0.0f, -11.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("complex2D") + << (qreal)2.0f << (qreal)-11.0f << (qreal)0.0f << (void *)complexTranslate2D; +} +void tst_QMatrixNxN::translate4x4() +{ + QFETCH(qreal, x); + QFETCH(qreal, y); + QFETCH(qreal, z); + QFETCH(void *, resultValues); + + QMatrix4x4 result((const qreal *)resultValues); + + QMatrix4x4 m1; + m1.translate(QVector3D(x, y, z)); + QVERIFY(isSame(m1, (const qreal *)resultValues)); + + QMatrix4x4 m2; + m2.translate(x, y, z); + QVERIFY(isSame(m2, (const qreal *)resultValues)); + + if (z == 0.0f) { + QMatrix4x4 m2b; + m2b.translate(x, y); + QVERIFY(m2b == m2); + } + + QVector3D v1(2.0f, 3.0f, -4.0f); + QVector3D v2 = m1 * v1; + QCOMPARE(v2.x(), (qreal)(2.0f + x)); + QCOMPARE(v2.y(), (qreal)(3.0f + y)); + QCOMPARE(v2.z(), (qreal)(-4.0f + z)); + + QVector4D v3(2.0f, 3.0f, -4.0f, 1.0f); + QVector4D v4 = m1 * v3; + QCOMPARE(v4.x(), (qreal)(2.0f + x)); + QCOMPARE(v4.y(), (qreal)(3.0f + y)); + QCOMPARE(v4.z(), (qreal)(-4.0f + z)); + QCOMPARE(v4.w(), (qreal)1.0f); + + QVector4D v5(2.0f, 3.0f, -4.0f, 34.0f); + QVector4D v6 = m1 * v5; + QCOMPARE(v6.x(), (qreal)(2.0f + x * 34.0f)); + QCOMPARE(v6.y(), (qreal)(3.0f + y * 34.0f)); + QCOMPARE(v6.z(), (qreal)(-4.0f + z * 34.0f)); + QCOMPARE(v6.w(), (qreal)34.0f); + + QPoint p1(2, 3); + QPoint p2 = m1 * p1; + QCOMPARE(p2.x(), (int)(2.0f + x)); + QCOMPARE(p2.y(), (int)(3.0f + y)); + + QPointF p3(2.0f, 3.0f); + QPointF p4 = m1 * p3; + QCOMPARE(p4.x(), (qreal)(2.0f + x)); + QCOMPARE(p4.y(), (qreal)(3.0f + y)); + + QMatrix4x4 m3(uniqueValues4); + QMatrix4x4 m4(m3); + m4.translate(x, y, z); + QVERIFY(m4 == m3 * m1); + + if (z == 0.0f) { + QMatrix4x4 m4b(m3); + m4b.translate(x, y); + QVERIFY(m4b == m4); + } +} + +// Test the generation and use of 4x4 rotation matrices. +void tst_QMatrixNxN::rotate4x4_data() +{ + QTest::addColumn<qreal>("angle"); + QTest::addColumn<qreal>("x"); + QTest::addColumn<qreal>("y"); + QTest::addColumn<qreal>("z"); + QTest::addColumn<void *>("resultValues"); + + static const qreal nullRotate[] = + {0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("null") + << (qreal)90.0f + << (qreal)0.0f << (qreal)0.0f << (qreal)0.0f + << (void *)nullRotate; + + static const qreal noRotate[] = + {1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("zerodegrees") + << (qreal)0.0f + << (qreal)2.0f << (qreal)3.0f << (qreal)-4.0f + << (void *)noRotate; + + static const qreal xRotate[] = + {1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, -1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("xrotate") + << (qreal)90.0f + << (qreal)1.0f << (qreal)0.0f << (qreal)0.0f + << (void *)xRotate; + + static const qreal xRotateNeg[] = + {1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("-xrotate") + << (qreal)90.0f + << (qreal)-1.0f << (qreal)0.0f << (qreal)0.0f + << (void *)xRotateNeg; + + static const qreal yRotate[] = + {0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("yrotate") + << (qreal)90.0f + << (qreal)0.0f << (qreal)1.0f << (qreal)0.0f + << (void *)yRotate; + + static const qreal yRotateNeg[] = + {0.0f, 0.0f, -1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + QTest::newRow("-yrotate") + << (qreal)90.0f + << (qreal)0.0f << (qreal)-1.0f << (qreal)0.0f + << (void *)yRotateNeg; + + static const qreal zRotate[] = + {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("zrotate") + << (qreal)90.0f + << (qreal)0.0f << (qreal)0.0f << (qreal)1.0f + << (void *)zRotate; + + static const qreal zRotateNeg[] = + {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("-zrotate") + << (qreal)90.0f + << (qreal)0.0f << (qreal)0.0f << (qreal)-1.0f + << (void *)zRotateNeg; + + // Algorithm from http://en.wikipedia.org/wiki/Rotation_matrix. + // Deliberately different from the one in the code for cross-checking. + static qreal complexRotate[16]; + qreal x = 1.0f; + qreal y = 2.0f; + qreal z = -6.0f; + qreal angle = -45.0f; + qreal c = qCos(angle * M_PI / 180.0f); + qreal s = qSin(angle * M_PI / 180.0f); + qreal len = qSqrt(x * x + y * y + z * z); + qreal xu = x / len; + qreal yu = y / len; + qreal zu = z / len; + complexRotate[0] = (qreal)((1 - xu * xu) * c + xu * xu); + complexRotate[1] = (qreal)(-zu * s - xu * yu * c + xu * yu); + complexRotate[2] = (qreal)(yu * s - xu * zu * c + xu * zu); + complexRotate[3] = 0; + complexRotate[4] = (qreal)(zu * s - xu * yu * c + xu * yu); + complexRotate[5] = (qreal)((1 - yu * yu) * c + yu * yu); + complexRotate[6] = (qreal)(-xu * s - yu * zu * c + yu * zu); + complexRotate[7] = 0; + complexRotate[8] = (qreal)(-yu * s - xu * zu * c + xu * zu); + complexRotate[9] = (qreal)(xu * s - yu * zu * c + yu * zu); + complexRotate[10] = (qreal)((1 - zu * zu) * c + zu * zu); + complexRotate[11] = 0; + complexRotate[12] = 0; + complexRotate[13] = 0; + complexRotate[14] = 0; + complexRotate[15] = 1; + + QTest::newRow("complex") + << (qreal)angle + << (qreal)x << (qreal)y << (qreal)z + << (void *)complexRotate; +} +void tst_QMatrixNxN::rotate4x4() +{ + QFETCH(qreal, angle); + QFETCH(qreal, x); + QFETCH(qreal, y); + QFETCH(qreal, z); + QFETCH(void *, resultValues); + + QMatrix4x4 m1; + m1.rotate(angle, QVector3D(x, y, z)); + QVERIFY(isSame(m1, (const qreal *)resultValues)); + + QMatrix4x4 m2; + m2.rotate(angle, x, y, z); + QVERIFY(isSame(m2, (const qreal *)resultValues)); + + QMatrix4x4 m3(uniqueValues4); + QMatrix4x4 m4(m3); + m4.rotate(angle, x, y, z); + QVERIFY(matrixFuzzyCompare(m4, m3 * m1)); + + // Null vectors don't make sense for quaternion rotations. + if (x != 0 || y != 0 || z != 0) { + QMatrix4x4 m5; + m5.rotate(QQuaternion::fromAxisAndAngle(QVector3D(x, y, z), angle)); + QVERIFY(isSame(m5, (const qreal *)resultValues)); + } + +#define ROTATE4(xin,yin,zin,win,xout,yout,zout,wout) \ + do { \ + xout = ((const qreal *)resultValues)[0] * xin + \ + ((const qreal *)resultValues)[1] * yin + \ + ((const qreal *)resultValues)[2] * zin + \ + ((const qreal *)resultValues)[3] * win; \ + yout = ((const qreal *)resultValues)[4] * xin + \ + ((const qreal *)resultValues)[5] * yin + \ + ((const qreal *)resultValues)[6] * zin + \ + ((const qreal *)resultValues)[7] * win; \ + zout = ((const qreal *)resultValues)[8] * xin + \ + ((const qreal *)resultValues)[9] * yin + \ + ((const qreal *)resultValues)[10] * zin + \ + ((const qreal *)resultValues)[11] * win; \ + wout = ((const qreal *)resultValues)[12] * xin + \ + ((const qreal *)resultValues)[13] * yin + \ + ((const qreal *)resultValues)[14] * zin + \ + ((const qreal *)resultValues)[15] * win; \ + } while (0) + + // Rotate various test vectors using the straight-forward approach. + qreal v1x, v1y, v1z, v1w; + ROTATE4(2.0f, 3.0f, -4.0f, 1.0f, v1x, v1y, v1z, v1w); + v1x /= v1w; + v1y /= v1w; + v1z /= v1w; + qreal v3x, v3y, v3z, v3w; + ROTATE4(2.0f, 3.0f, -4.0f, 1.0f, v3x, v3y, v3z, v3w); + qreal v5x, v5y, v5z, v5w; + ROTATE4(2.0f, 3.0f, -4.0f, 34.0f, v5x, v5y, v5z, v5w); + qreal p1x, p1y, p1z, p1w; + ROTATE4(2.0f, 3.0f, 0.0f, 1.0f, p1x, p1y, p1z, p1w); + p1x /= p1w; + p1y /= p1w; + p1z /= p1w; + + QVector3D v1(2.0f, 3.0f, -4.0f); + QVector3D v2 = m1 * v1; + QVERIFY(fuzzyCompare(v2.x(), v1x)); + QVERIFY(fuzzyCompare(v2.y(), v1y)); + QVERIFY(fuzzyCompare(v2.z(), v1z)); + + QVector4D v3(2.0f, 3.0f, -4.0f, 1.0f); + QVector4D v4 = m1 * v3; + QVERIFY(fuzzyCompare(v4.x(), v3x)); + QVERIFY(fuzzyCompare(v4.y(), v3y)); + QVERIFY(fuzzyCompare(v4.z(), v3z)); + QVERIFY(fuzzyCompare(v4.w(), v3w)); + + QVector4D v5(2.0f, 3.0f, -4.0f, 34.0f); + QVector4D v6 = m1 * v5; + QVERIFY(fuzzyCompare(v6.x(), v5x)); + QVERIFY(fuzzyCompare(v6.y(), v5y)); + QVERIFY(fuzzyCompare(v6.z(), v5z)); + QVERIFY(fuzzyCompare(v6.w(), v5w)); + + QPoint p1(2, 3); + QPoint p2 = m1 * p1; + QCOMPARE(p2.x(), qRound(p1x)); + QCOMPARE(p2.y(), qRound(p1y)); + + QPointF p3(2.0f, 3.0f); + QPointF p4 = m1 * p3; + QVERIFY(fuzzyCompare((float)(p4.x()), p1x)); + QVERIFY(fuzzyCompare((float)(p4.y()), p1y)); + + if (x != 0 || y != 0 || z != 0) { + QQuaternion q = QQuaternion::fromAxisAndAngle(QVector3D(x, y, z), angle); + QVector3D vq = q.rotateVector(v1); + QVERIFY(fuzzyCompare(vq.x(), v1x)); + QVERIFY(fuzzyCompare(vq.y(), v1y)); + QVERIFY(fuzzyCompare(vq.z(), v1z)); + } +} + +static bool isSame(const QMatrix3x3& m1, const Matrix3& m2) +{ + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) { + if (!fuzzyCompare(m1(row, col), m2.v[row * 3 + col])) + return false; + } + } + return true; +} + +// Test the computation of normal matrices from 4x4 transformation matrices. +void tst_QMatrixNxN::normalMatrix_data() +{ + QTest::addColumn<void *>("mValues"); + + QTest::newRow("identity") + << (void *)identityValues4; + QTest::newRow("unique") + << (void *)uniqueValues4; // Not invertible because determinant == 0. + + static qreal const translateValues[16] = + {1.0f, 0.0f, 0.0f, 4.0f, + 0.0f, 1.0f, 0.0f, 5.0f, + 0.0f, 0.0f, 1.0f, -3.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + static qreal const scaleValues[16] = + {2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 7.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 9.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + static qreal const bothValues[16] = + {2.0f, 0.0f, 0.0f, 4.0f, + 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 nullScaleValues1[16] = + {0.0f, 0.0f, 0.0f, 4.0f, + 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 nullScaleValues2[16] = + {2.0f, 0.0f, 0.0f, 4.0f, + 0.0f, 0.0f, 0.0f, 5.0f, + 0.0f, 0.0f, 9.0f, -3.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + static qreal const nullScaleValues3[16] = + {2.0f, 0.0f, 0.0f, 4.0f, + 0.0f, 7.0f, 0.0f, 5.0f, + 0.0f, 0.0f, 0.0f, -3.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + + QTest::newRow("translate") << (void *)translateValues; + QTest::newRow("scale") << (void *)scaleValues; + QTest::newRow("both") << (void *)bothValues; + QTest::newRow("null scale 1") << (void *)nullScaleValues1; + QTest::newRow("null scale 2") << (void *)nullScaleValues2; + QTest::newRow("null scale 3") << (void *)nullScaleValues3; +} +void tst_QMatrixNxN::normalMatrix() +{ + QFETCH(void *, mValues); + const qreal *values = (const qreal *)mValues; + + // Compute the expected answer the long way. + Matrix3 min; + Matrix3 answer; + min.v[0] = values[0]; + min.v[1] = values[1]; + min.v[2] = values[2]; + min.v[3] = values[4]; + min.v[4] = values[5]; + min.v[5] = values[6]; + min.v[6] = values[8]; + min.v[7] = values[9]; + min.v[8] = values[10]; + bool invertible = m3Inverse(min, answer); + m3Transpose(answer); + + // Perform the test. + QMatrix4x4 m1(values); + QMatrix3x3 n1 = m1.normalMatrix(); + + if (invertible) + QVERIFY(::isSame(n1, answer)); + else + QVERIFY(isIdentity(n1)); + + // Perform the test again, after inferring special matrix types. + // This tests the optimized paths in the normalMatrix() function. + m1.inferSpecialType(); + n1 = m1.normalMatrix(); + + if (invertible) + QVERIFY(::isSame(n1, answer)); + else + QVERIFY(isIdentity(n1)); +} + +// Test optimized transformations on 4x4 matrices. +void tst_QMatrixNxN::optimizedTransforms() +{ + static qreal const translateValues[16] = + {1.0f, 0.0f, 0.0f, 4.0f, + 0.0f, 1.0f, 0.0f, 5.0f, + 0.0f, 0.0f, 1.0f, -3.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + static qreal const translateDoubleValues[16] = + {1.0f, 0.0f, 0.0f, 8.0f, + 0.0f, 1.0f, 0.0f, 10.0f, + 0.0f, 0.0f, 1.0f, -6.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + static qreal const scaleValues[16] = + {2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 7.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 9.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + static qreal const scaleDoubleValues[16] = + {4.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 49.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 81.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + static qreal const bothValues[16] = + {2.0f, 0.0f, 0.0f, 4.0f, + 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 bothReverseValues[16] = + {2.0f, 0.0f, 0.0f, 4.0f * 2.0f, + 0.0f, 7.0f, 0.0f, 5.0f * 7.0f, + 0.0f, 0.0f, 9.0f, -3.0f * 9.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + static qreal const bothThenTranslateValues[16] = + {2.0f, 0.0f, 0.0f, 4.0f + 2.0f * 4.0f, + 0.0f, 7.0f, 0.0f, 5.0f + 7.0f * 5.0f, + 0.0f, 0.0f, 9.0f, -3.0f + 9.0f * -3.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + static qreal const bothThenScaleValues[16] = + {4.0f, 0.0f, 0.0f, 4.0f, + 0.0f, 49.0f, 0.0f, 5.0f, + 0.0f, 0.0f, 81.0f, -3.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + + QMatrix4x4 translate(translateValues); + QMatrix4x4 scale(scaleValues); + QMatrix4x4 both(bothValues); + + QMatrix4x4 m1; + m1.translate(4.0f, 5.0f, -3.0f); + QVERIFY(isSame(m1, translateValues)); + m1.translate(4.0f, 5.0f, -3.0f); + QVERIFY(isSame(m1, translateDoubleValues)); + + QMatrix4x4 m2; + m2.translate(QVector3D(4.0f, 5.0f, -3.0f)); + QVERIFY(isSame(m2, translateValues)); + m2.translate(QVector3D(4.0f, 5.0f, -3.0f)); + QVERIFY(isSame(m2, translateDoubleValues)); + + QMatrix4x4 m3; + m3.scale(2.0f, 7.0f, 9.0f); + QVERIFY(isSame(m3, scaleValues)); + m3.scale(2.0f, 7.0f, 9.0f); + QVERIFY(isSame(m3, scaleDoubleValues)); + + QMatrix4x4 m4; + m4.scale(QVector3D(2.0f, 7.0f, 9.0f)); + QVERIFY(isSame(m4, scaleValues)); + m4.scale(QVector3D(2.0f, 7.0f, 9.0f)); + QVERIFY(isSame(m4, scaleDoubleValues)); + + QMatrix4x4 m5; + m5.translate(4.0f, 5.0f, -3.0f); + m5.scale(2.0f, 7.0f, 9.0f); + QVERIFY(isSame(m5, bothValues)); + m5.translate(4.0f, 5.0f, -3.0f); + QVERIFY(isSame(m5, bothThenTranslateValues)); + + QMatrix4x4 m6; + m6.translate(QVector3D(4.0f, 5.0f, -3.0f)); + m6.scale(QVector3D(2.0f, 7.0f, 9.0f)); + QVERIFY(isSame(m6, bothValues)); + m6.translate(QVector3D(4.0f, 5.0f, -3.0f)); + QVERIFY(isSame(m6, bothThenTranslateValues)); + + QMatrix4x4 m7; + m7.scale(2.0f, 7.0f, 9.0f); + m7.translate(4.0f, 5.0f, -3.0f); + QVERIFY(isSame(m7, bothReverseValues)); + + QMatrix4x4 m8; + m8.scale(QVector3D(2.0f, 7.0f, 9.0f)); + m8.translate(QVector3D(4.0f, 5.0f, -3.0f)); + QVERIFY(isSame(m8, bothReverseValues)); + + QMatrix4x4 m9; + m9.translate(4.0f, 5.0f, -3.0f); + m9.scale(2.0f, 7.0f, 9.0f); + QVERIFY(isSame(m9, bothValues)); + m9.scale(2.0f, 7.0f, 9.0f); + QVERIFY(isSame(m9, bothThenScaleValues)); + + QMatrix4x4 m10; + m10.translate(QVector3D(4.0f, 5.0f, -3.0f)); + m10.scale(QVector3D(2.0f, 7.0f, 9.0f)); + QVERIFY(isSame(m10, bothValues)); + m10.scale(QVector3D(2.0f, 7.0f, 9.0f)); + QVERIFY(isSame(m10, bothThenScaleValues)); +} + +// Test orthographic projections. +void tst_QMatrixNxN::ortho() +{ + QMatrix4x4 m1; + m1.ortho(QRect(0, 0, 300, 150)); + QPointF p1 = m1 * QPointF(0, 0); + QPointF p2 = m1 * QPointF(300, 0); + QPointF p3 = m1 * QPointF(0, 150); + QPointF p4 = m1 * QPointF(300, 150); + QVector3D p5 = m1 * QVector3D(300, 150, 1); + QVERIFY(fuzzyCompare(p1.x(), -1.0)); + QVERIFY(fuzzyCompare(p1.y(), 1.0)); + QVERIFY(fuzzyCompare(p2.x(), 1.0)); + QVERIFY(fuzzyCompare(p2.y(), 1.0)); + QVERIFY(fuzzyCompare(p3.x(), -1.0)); + QVERIFY(fuzzyCompare(p3.y(), -1.0)); + QVERIFY(fuzzyCompare(p4.x(), 1.0)); + QVERIFY(fuzzyCompare(p4.y(), -1.0)); + QVERIFY(fuzzyCompare(p5.x(), (qreal)1.0)); + QVERIFY(fuzzyCompare(p5.y(), (qreal)-1.0)); + QVERIFY(fuzzyCompare(p5.z(), (qreal)-1.0)); + + QMatrix4x4 m2; + m2.ortho(QRectF(0, 0, 300, 150)); + p1 = m2 * QPointF(0, 0); + p2 = m2 * QPointF(300, 0); + p3 = m2 * QPointF(0, 150); + p4 = m2 * QPointF(300, 150); + p5 = m2 * QVector3D(300, 150, 1); + QVERIFY(fuzzyCompare(p1.x(), -1.0)); + QVERIFY(fuzzyCompare(p1.y(), 1.0)); + QVERIFY(fuzzyCompare(p2.x(), 1.0)); + QVERIFY(fuzzyCompare(p2.y(), 1.0)); + QVERIFY(fuzzyCompare(p3.x(), -1.0)); + QVERIFY(fuzzyCompare(p3.y(), -1.0)); + QVERIFY(fuzzyCompare(p4.x(), 1.0)); + QVERIFY(fuzzyCompare(p4.y(), -1.0)); + QVERIFY(fuzzyCompare(p5.x(), (qreal)1.0)); + QVERIFY(fuzzyCompare(p5.y(), (qreal)-1.0)); + QVERIFY(fuzzyCompare(p5.z(), (qreal)-1.0)); + + QMatrix4x4 m3; + m3.ortho(0, 300, 150, 0, -1, 1); + p1 = m3 * QPointF(0, 0); + p2 = m3 * QPointF(300, 0); + p3 = m3 * QPointF(0, 150); + p4 = m3 * QPointF(300, 150); + p5 = m3 * QVector3D(300, 150, 1); + QVERIFY(fuzzyCompare(p1.x(), -1.0)); + QVERIFY(fuzzyCompare(p1.y(), 1.0)); + QVERIFY(fuzzyCompare(p2.x(), 1.0)); + QVERIFY(fuzzyCompare(p2.y(), 1.0)); + QVERIFY(fuzzyCompare(p3.x(), -1.0)); + QVERIFY(fuzzyCompare(p3.y(), -1.0)); + QVERIFY(fuzzyCompare(p4.x(), 1.0)); + QVERIFY(fuzzyCompare(p4.y(), -1.0)); + QVERIFY(fuzzyCompare(p5.x(), (qreal)1.0)); + QVERIFY(fuzzyCompare(p5.y(), (qreal)-1.0)); + QVERIFY(fuzzyCompare(p5.z(), (qreal)-1.0)); + + QMatrix4x4 m4; + m4.ortho(0, 300, 150, 0, -2, 3); + p1 = m4 * QPointF(0, 0); + p2 = m4 * QPointF(300, 0); + p3 = m4 * QPointF(0, 150); + p4 = m4 * QPointF(300, 150); + p5 = m4 * QVector3D(300, 150, 1); + QVERIFY(fuzzyCompare(p1.x(), -1.0)); + QVERIFY(fuzzyCompare(p1.y(), 1.0)); + QVERIFY(fuzzyCompare(p2.x(), 1.0)); + QVERIFY(fuzzyCompare(p2.y(), 1.0)); + QVERIFY(fuzzyCompare(p3.x(), -1.0)); + QVERIFY(fuzzyCompare(p3.y(), -1.0)); + QVERIFY(fuzzyCompare(p4.x(), 1.0)); + QVERIFY(fuzzyCompare(p4.y(), -1.0)); + QVERIFY(fuzzyCompare(p5.x(), (qreal)1.0)); + QVERIFY(fuzzyCompare(p5.y(), (qreal)-1.0)); + QVERIFY(fuzzyCompare(p5.z(), (qreal)-0.6)); + + // An empty view volume should leave the matrix alone. + QMatrix4x4 m5; + m5.ortho(0, 0, 150, 0, -2, 3); + QVERIFY(m5.isIdentity()); + m5.ortho(0, 300, 150, 150, -2, 3); + QVERIFY(m5.isIdentity()); + m5.ortho(0, 300, 150, 0, 2, 2); + QVERIFY(m5.isIdentity()); +} + +// Test perspective frustum projections. +void tst_QMatrixNxN::frustum() +{ + QMatrix4x4 m1; + m1.frustum(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f); + QVector3D p1 = m1 * QVector3D(-1.0f, -1.0f, 1.0f); + QVector3D p2 = m1 * QVector3D(1.0f, -1.0f, 1.0f); + QVector3D p3 = m1 * QVector3D(-1.0f, 1.0f, 1.0f); + QVector3D p4 = m1 * QVector3D(1.0f, 1.0f, 1.0f); + QVector3D p5 = m1 * QVector3D(0.0f, 0.0f, 2.0f); + QVERIFY(fuzzyCompare(p1.x(), -1.0f)); + QVERIFY(fuzzyCompare(p1.y(), -1.0f)); + QVERIFY(fuzzyCompare(p1.z(), -1.0f)); + QVERIFY(fuzzyCompare(p2.x(), 1.0f)); + QVERIFY(fuzzyCompare(p2.y(), -1.0f)); + QVERIFY(fuzzyCompare(p2.z(), -1.0f)); + QVERIFY(fuzzyCompare(p3.x(), -1.0f)); + QVERIFY(fuzzyCompare(p3.y(), 1.0f)); + QVERIFY(fuzzyCompare(p3.z(), -1.0f)); + QVERIFY(fuzzyCompare(p4.x(), 1.0f)); + QVERIFY(fuzzyCompare(p4.y(), 1.0f)); + QVERIFY(fuzzyCompare(p4.z(), -1.0f)); + QVERIFY(fuzzyCompare(p5.x(), 0.0f)); + QVERIFY(fuzzyCompare(p5.y(), 0.0f)); + QVERIFY(fuzzyCompare(p5.z(), -0.5f)); + + // An empty view volume should leave the matrix alone. + QMatrix4x4 m5; + m5.frustum(0, 0, 150, 0, -2, 3); + QVERIFY(m5.isIdentity()); + m5.frustum(0, 300, 150, 150, -2, 3); + QVERIFY(m5.isIdentity()); + m5.frustum(0, 300, 150, 0, 2, 2); + QVERIFY(m5.isIdentity()); +} + +// Test perspective field-of-view projections. +void tst_QMatrixNxN::perspective() +{ + QMatrix4x4 m1; + m1.perspective(45.0f, 1.0f, -1.0f, 1.0f); + QVector3D p1 = m1 * QVector3D(-1.0f, -1.0f, 1.0f); + QVector3D p2 = m1 * QVector3D(1.0f, -1.0f, 1.0f); + QVector3D p3 = m1 * QVector3D(-1.0f, 1.0f, 1.0f); + QVector3D p4 = m1 * QVector3D(1.0f, 1.0f, 1.0f); + QVector3D p5 = m1 * QVector3D(0.0f, 0.0f, 2.0f); + QVERIFY(fuzzyCompare(p1.x(), 2.41421)); + QVERIFY(fuzzyCompare(p1.y(), 2.41421)); + QVERIFY(fuzzyCompare(p1.z(), -1)); + QVERIFY(fuzzyCompare(p2.x(), -2.41421)); + QVERIFY(fuzzyCompare(p2.y(), 2.41421)); + QVERIFY(fuzzyCompare(p2.z(), -1.0f)); + QVERIFY(fuzzyCompare(p3.x(), 2.41421)); + QVERIFY(fuzzyCompare(p3.y(), -2.41421)); + QVERIFY(fuzzyCompare(p3.z(), -1.0f)); + QVERIFY(fuzzyCompare(p4.x(), -2.41421)); + QVERIFY(fuzzyCompare(p4.y(), -2.41421)); + QVERIFY(fuzzyCompare(p4.z(), -1.0f)); + QVERIFY(fuzzyCompare(p5.x(), 0.0f)); + QVERIFY(fuzzyCompare(p5.y(), 0.0f)); + QVERIFY(fuzzyCompare(p5.z(), -0.5f)); + + // An empty view volume should leave the matrix alone. + QMatrix4x4 m5; + m5.perspective(45.0f, 1.0f, 0.0f, 0.0f); + QVERIFY(m5.isIdentity()); + m5.perspective(45.0f, 0.0f, -1.0f, 1.0f); + QVERIFY(m5.isIdentity()); + m5.perspective(0.0f, 1.0f, -1.0f, 1.0f); + QVERIFY(m5.isIdentity()); +} + +// Test left-handed vs right-handed coordinate flipping. +void tst_QMatrixNxN::flipCoordinates() +{ + QMatrix4x4 m1; + m1.flipCoordinates(); + QVector3D p1 = m1 * QVector3D(2, 3, 4); + QVERIFY(p1 == QVector3D(2, -3, -4)); + + QMatrix4x4 m2; + m2.scale(2.0f, 3.0f, 1.0f); + m2.flipCoordinates(); + QVector3D p2 = m2 * QVector3D(2, 3, 4); + QVERIFY(p2 == QVector3D(4, -9, -4)); + + QMatrix4x4 m3; + m3.translate(2.0f, 3.0f, 1.0f); + m3.flipCoordinates(); + QVector3D p3 = m3 * QVector3D(2, 3, 4); + QVERIFY(p3 == QVector3D(4, 0, -3)); + + QMatrix4x4 m4; + m4.rotate(90.0f, 0.0f, 0.0f, 1.0f); + m4.flipCoordinates(); + QVector3D p4 = m4 * QVector3D(2, 3, 4); + QVERIFY(p4 == QVector3D(3, 2, -4)); +} + +// Test conversion of generic matrices to and from the non-generic types. +void tst_QMatrixNxN::convertGeneric() +{ + QMatrix4x3 m1(uniqueValues4x3); + + static qreal const unique4x4[16] = { + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; +#if !defined(QT_NO_MEMBER_TEMPLATES) + QMatrix4x4 m4(m1); + QVERIFY(isSame(m4, unique4x4)); +#endif + QMatrix4x4 m5 = qGenericMatrixToMatrix4x4(m1); + QVERIFY(isSame(m5, unique4x4)); + + static qreal const conv4x4[12] = { + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f + }; + QMatrix4x4 m9(uniqueValues4); +#if !defined(QT_NO_MEMBER_TEMPLATES) + QMatrix4x3 m10 = m9.toGenericMatrix<4, 3>(); + QVERIFY(isSame(m10, conv4x4)); +#endif + + QMatrix4x3 m11 = qGenericMatrixFromMatrix4x4<4, 3>(m9); + QVERIFY(isSame(m11, conv4x4)); +} + +void tst_QMatrixNxN::extractAxisRotation_data() +{ + QTest::addColumn<float>("x"); + QTest::addColumn<float>("y"); + QTest::addColumn<float>("z"); + QTest::addColumn<float>("angle"); + + QTest::newRow("1, 0, 0, 0 deg") << 1.0f << 0.0f << 0.0f << 0.0f; + QTest::newRow("1, 0, 0, 90 deg") << 1.0f << 0.0f << 0.0f << 90.0f; + QTest::newRow("1, 0, 0, 270 deg") << 1.0f << 0.0f << 0.0f << 270.0f; + QTest::newRow("1, 0, 0, 45 deg") << 1.0f << 0.0f << 0.0f << 45.0f; + QTest::newRow("1, 0, 0, 120 deg") << 1.0f << 0.0f << 0.0f << 120.0f; + QTest::newRow("1, 0, 0, 300 deg") << 1.0f << 0.0f << 0.0f << 300.0f; + + QTest::newRow("0, 1, 0, 90 deg") << 0.0f << 1.0f << 0.0f << 90.0f; + QTest::newRow("0, 1, 0, 270 deg") << 0.0f << 1.0f << 0.0f << 270.0f; + QTest::newRow("0, 1, 0, 45 deg") << 0.0f << 1.0f << 0.0f << 45.0f; + QTest::newRow("0, 1, 0, 120 deg") << 0.0f << 1.0f << 0.0f << 120.0f; + QTest::newRow("0, 1, 0, 300 deg") << 0.0f << 1.0f << 0.0f << 300.0f; + + QTest::newRow("0, 0, 1, 90 deg") << 0.0f << 0.0f << 1.0f << 90.0f; + QTest::newRow("0, 0, 1, 270 deg") << 0.0f << 0.0f << 1.0f << 270.0f; + QTest::newRow("0, 0, 1, 45 deg") << 0.0f << 0.0f << 1.0f << 45.0f; + QTest::newRow("0, 0, 1, 120 deg") << 0.0f << 0.0f << 1.0f << 120.0f; + QTest::newRow("0, 0, 1, 300 deg") << 0.0f << 0.0f << 1.0f << 300.0f; + + QTest::newRow("1, 1, 1, 90 deg") << 1.0f << 1.0f << 1.0f << 90.0f; + QTest::newRow("1, 1, 1, 270 deg") << 1.0f << 1.0f << 1.0f << 270.0f; + QTest::newRow("1, 1, 1, 45 deg") << 1.0f << 1.0f << 1.0f << 45.0f; + QTest::newRow("1, 1, 1, 120 deg") << 1.0f << 1.0f << 1.0f << 120.0f; + QTest::newRow("1, 1, 1, 300 deg") << 1.0f << 1.0f << 1.0f << 300.0f; +} + +void tst_QMatrixNxN::extractAxisRotation() +{ + QFETCH(float, x); + QFETCH(float, y); + QFETCH(float, z); + QFETCH(float, angle); + + QMatrix4x4 m; + QVector3D origAxis(x, y, z); + + m.rotate(angle, x, y, z); + + origAxis.normalize(); + QVector3D extractedAxis; + qreal extractedAngle; + + m.extractAxisRotation(extractedAngle, extractedAxis); + + qreal epsilon = 0.001; + + if (angle > 180) { + QVERIFY(fuzzyCompare(360.0f - angle, extractedAngle, epsilon)); + QVERIFY(fuzzyCompare(extractedAxis, -origAxis, epsilon)); + } else { + QVERIFY(fuzzyCompare(angle, extractedAngle, epsilon)); + QVERIFY(fuzzyCompare(extractedAxis, origAxis, epsilon)); + } +} + +void tst_QMatrixNxN::extractTranslation_data() +{ + QTest::addColumn<QMatrix4x4>("rotation"); + QTest::addColumn<float>("x"); + QTest::addColumn<float>("y"); + QTest::addColumn<float>("z"); + + static QMatrix4x4 m1; + + QTest::newRow("identity, 100, 50, 25") + << m1 << 100.0f << 50.0f << 250.0f; + + m1.rotate(45.0, 1.0, 0.0, 0.0); + QTest::newRow("rotX 45 + 100, 50, 25") << m1 << 100.0f << 50.0f << 25.0f; + + m1.setIdentity(); + m1.rotate(45.0, 0.0, 1.0, 0.0); + QTest::newRow("rotY 45 + 100, 50, 25") << m1 << 100.0f << 50.0f << 25.0f; + + m1.setIdentity(); + m1.rotate(75, 0.0, 0.0, 1.0); + m1.rotate(25, 1.0, 0.0, 0.0); + m1.rotate(45, 0.0, 1.0, 0.0); + QTest::newRow("rotZ 75, rotX 25, rotY 45, 100, 50, 25") << m1 << 100.0f << 50.0f << 25.0f; +} + +void tst_QMatrixNxN::extractTranslation() +{ + QFETCH(QMatrix4x4, rotation); + QFETCH(float, x); + QFETCH(float, y); + QFETCH(float, z); + + rotation.translate(x, y, z); + + QVector3D vec = rotation.extractTranslation(); + + qreal epsilon = 0.001; + + QVERIFY(fuzzyCompare(vec.x(), x, epsilon)); + QVERIFY(fuzzyCompare(vec.y(), y, epsilon)); + QVERIFY(fuzzyCompare(vec.z(), z, epsilon)); + + QMatrix4x4 lookAt; + QVector3D eye(1.5f, -2.5f, 2.5f); + lookAt.lookAt(eye, + QVector3D(10.0f, 10.0f, 10.0f), + QVector3D(0.0f, 1.0f, 0.0f)); + + QVector3D extEye = lookAt.extractTranslation(); + + QVERIFY(fuzzyCompare(eye.x(), -extEye.x(), epsilon)); + QVERIFY(fuzzyCompare(eye.y(), -extEye.y(), epsilon)); + QVERIFY(fuzzyCompare(eye.z(), -extEye.z(), epsilon)); +} + +// 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 +}; + +// Structure that allows direct access to "flagBits" for testing. +struct Matrix4x4 +{ + float m[4][4]; + int flagBits; +}; + +// Test the inferring of special matrix types. +void tst_QMatrixNxN::inferSpecialType_data() +{ + QTest::addColumn<void *>("mValues"); + QTest::addColumn<int>("flagBits"); + + QTest::newRow("null") + << (void *)nullValues4 << (int)General; + QTest::newRow("identity") + << (void *)identityValues4 << (int)Identity; + QTest::newRow("unique") + << (void *)uniqueValues4 << (int)General; + + static qreal scaleValues[16] = { + 2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 3.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 4.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + QTest::newRow("scale") + << (void *)scaleValues << (int)Scale; + + static qreal translateValues[16] = { + 1.0f, 0.0f, 0.0f, 2.0f, + 0.0f, 1.0f, 0.0f, 3.0f, + 0.0f, 0.0f, 1.0f, 4.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + QTest::newRow("scale") + << (void *)translateValues << (int)Translation; + + static qreal bothValues[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); + + static qreal belowValues[16] = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 4.0f, 0.0f, 0.0f, 1.0f + }; + QTest::newRow("below") + << (void *)belowValues << (int)General; +} +void tst_QMatrixNxN::inferSpecialType() +{ + QFETCH(void *, mValues); + QFETCH(int, flagBits); + + QMatrix4x4 m((const qreal *)mValues); + m.inferSpecialType(); + + QCOMPARE(reinterpret_cast<Matrix4x4 *>(&m)->flagBits, flagBits); +} + +void tst_QMatrixNxN::columnsAndRows() +{ + QMatrix4x4 m1(uniqueValues4); + + QVERIFY(m1.column(0) == QVector4D(1, 5, 9, 13)); + QVERIFY(m1.column(1) == QVector4D(2, 6, 10, 14)); + QVERIFY(m1.column(2) == QVector4D(3, 7, 11, 15)); + QVERIFY(m1.column(3) == QVector4D(4, 8, 12, 16)); + + QVERIFY(m1.row(0) == QVector4D(1, 2, 3, 4)); + QVERIFY(m1.row(1) == QVector4D(5, 6, 7, 8)); + QVERIFY(m1.row(2) == QVector4D(9, 10, 11, 12)); + QVERIFY(m1.row(3) == QVector4D(13, 14, 15, 16)); + + m1.setColumn(0, QVector4D(-1, -5, -9, -13)); + m1.setColumn(1, QVector4D(-2, -6, -10, -14)); + m1.setColumn(2, QVector4D(-3, -7, -11, -15)); + m1.setColumn(3, QVector4D(-4, -8, -12, -16)); + + QVERIFY(m1.column(0) == QVector4D(-1, -5, -9, -13)); + QVERIFY(m1.column(1) == QVector4D(-2, -6, -10, -14)); + QVERIFY(m1.column(2) == QVector4D(-3, -7, -11, -15)); + QVERIFY(m1.column(3) == QVector4D(-4, -8, -12, -16)); + + QVERIFY(m1.row(0) == QVector4D(-1, -2, -3, -4)); + QVERIFY(m1.row(1) == QVector4D(-5, -6, -7, -8)); + QVERIFY(m1.row(2) == QVector4D(-9, -10, -11, -12)); + QVERIFY(m1.row(3) == QVector4D(-13, -14, -15, -16)); + + m1.setRow(0, QVector4D(1, 5, 9, 13)); + m1.setRow(1, QVector4D(2, 6, 10, 14)); + m1.setRow(2, QVector4D(3, 7, 11, 15)); + m1.setRow(3, QVector4D(4, 8, 12, 16)); + + QVERIFY(m1.column(0) == QVector4D(1, 2, 3, 4)); + QVERIFY(m1.column(1) == QVector4D(5, 6, 7, 8)); + QVERIFY(m1.column(2) == QVector4D(9, 10, 11, 12)); + QVERIFY(m1.column(3) == QVector4D(13, 14, 15, 16)); + + QVERIFY(m1.row(0) == QVector4D(1, 5, 9, 13)); + QVERIFY(m1.row(1) == QVector4D(2, 6, 10, 14)); + QVERIFY(m1.row(2) == QVector4D(3, 7, 11, 15)); + QVERIFY(m1.row(3) == QVector4D(4, 8, 12, 16)); +} + +// Test converting QMatrix objects into QMatrix4x4 and then +// checking that transformations in the original perform the +// equivalent transformations in the new matrix. +void tst_QMatrixNxN::convertQMatrix() +{ + QMatrix m1; + m1.translate(-3.5, 2.0); + QPointF p1 = m1.map(QPointF(100.0, 150.0)); + QCOMPARE(p1.x(), 100.0 - 3.5); + QCOMPARE(p1.y(), 150.0 + 2.0); + + QMatrix4x4 m2(m1); + QPointF p2 = m2 * QPointF(100.0, 150.0); + QCOMPARE((double)p2.x(), 100.0 - 3.5); + QCOMPARE((double)p2.y(), 150.0 + 2.0); + QVERIFY(m1 == m2.toAffine()); + + QMatrix m3; + m3.scale(1.5, -2.0); + QPointF p3 = m3.map(QPointF(100.0, 150.0)); + QCOMPARE(p3.x(), 1.5 * 100.0); + QCOMPARE(p3.y(), -2.0 * 150.0); + + QMatrix4x4 m4(m3); + QPointF p4 = m4 * QPointF(100.0, 150.0); + QCOMPARE((double)p4.x(), 1.5 * 100.0); + QCOMPARE((double)p4.y(), -2.0 * 150.0); + QVERIFY(m3 == m4.toAffine()); + + QMatrix m5; + m5.rotate(45.0); + QPointF p5 = m5.map(QPointF(100.0, 150.0)); + + QMatrix4x4 m6(m5); + QPointF p6 = m6 * QPointF(100.0, 150.0); + QVERIFY(fuzzyCompare(p5.x(), p6.x(), 0.005)); + QVERIFY(fuzzyCompare(p5.y(), p6.y(), 0.005)); + + QMatrix m7 = m6.toAffine(); + QVERIFY(fuzzyCompare(m5.m11(), m7.m11())); + QVERIFY(fuzzyCompare(m5.m12(), m7.m12())); + QVERIFY(fuzzyCompare(m5.m21(), m7.m21())); + QVERIFY(fuzzyCompare(m5.m22(), m7.m22())); + QVERIFY(fuzzyCompare(m5.dx(), m7.dx())); + QVERIFY(fuzzyCompare(m5.dy(), m7.dy())); +} + +// Test converting QTransform objects into QMatrix4x4 and then +// checking that transformations in the original perform the +// equivalent transformations in the new matrix. +void tst_QMatrixNxN::convertQTransform() +{ + QTransform m1; + m1.translate(-3.5, 2.0); + QPointF p1 = m1.map(QPointF(100.0, 150.0)); + QCOMPARE(p1.x(), 100.0 - 3.5); + QCOMPARE(p1.y(), 150.0 + 2.0); + + QMatrix4x4 m2(m1); + QPointF p2 = m2 * QPointF(100.0, 150.0); + QCOMPARE((double)p2.x(), 100.0 - 3.5); + QCOMPARE((double)p2.y(), 150.0 + 2.0); + QVERIFY(m1 == m2.toTransform()); + + QTransform m3; + m3.scale(1.5, -2.0); + QPointF p3 = m3.map(QPointF(100.0, 150.0)); + QCOMPARE(p3.x(), 1.5 * 100.0); + QCOMPARE(p3.y(), -2.0 * 150.0); + + QMatrix4x4 m4(m3); + QPointF p4 = m4 * QPointF(100.0, 150.0); + QCOMPARE((double)p4.x(), 1.5 * 100.0); + QCOMPARE((double)p4.y(), -2.0 * 150.0); + QVERIFY(m3 == m4.toTransform()); + + QTransform m5; + m5.rotate(45.0); + QPointF p5 = m5.map(QPointF(100.0, 150.0)); + + QMatrix4x4 m6(m5); + QPointF p6 = m6 * QPointF(100.0, 150.0); + QVERIFY(fuzzyCompare(p5.x(), p6.x(), 0.005)); + QVERIFY(fuzzyCompare(p5.y(), p6.y(), 0.005)); + + QTransform m7 = m6.toTransform(); + QVERIFY(fuzzyCompare(m5.m11(), m7.m11())); + QVERIFY(fuzzyCompare(m5.m12(), m7.m12())); + QVERIFY(fuzzyCompare(m5.m21(), m7.m21())); + QVERIFY(fuzzyCompare(m5.m22(), m7.m22())); + QVERIFY(fuzzyCompare(m5.dx(), m7.dx())); + QVERIFY(fuzzyCompare(m5.dy(), m7.dy())); + QVERIFY(fuzzyCompare(m5.m13(), m7.m13())); + QVERIFY(fuzzyCompare(m5.m23(), m7.m23())); + QVERIFY(fuzzyCompare(m5.m33(), m7.m33())); +} + +// Test filling matrices with specific values. +void tst_QMatrixNxN::fill() +{ + QMatrix4x4 m1; + m1.fill(0.0f); + QVERIFY(isSame(m1, nullValues4)); + + static const qreal fillValues4[] = + {2.5f, 2.5f, 2.5f, 2.5f, + 2.5f, 2.5f, 2.5f, 2.5f, + 2.5f, 2.5f, 2.5f, 2.5f, + 2.5f, 2.5f, 2.5f, 2.5f}; + m1.fill(2.5f); + QVERIFY(isSame(m1, fillValues4)); + + QMatrix4x3 m2; + m2.fill(0.0f); + QVERIFY(isSame(m2, nullValues4x3)); + + static const qreal fillValues4x3[] = + {2.5f, 2.5f, 2.5f, 2.5f, + 2.5f, 2.5f, 2.5f, 2.5f, + 2.5f, 2.5f, 2.5f, 2.5f}; + m2.fill(2.5f); + QVERIFY(isSame(m2, fillValues4x3)); +} + +// Test the mapRect() function for QRect and QRectF. +void tst_QMatrixNxN::mapRect_data() +{ + QTest::addColumn<qreal>("x"); + QTest::addColumn<qreal>("y"); + QTest::addColumn<qreal>("width"); + QTest::addColumn<qreal>("height"); + + QTest::newRow("null") + << (qreal)0.0f << (qreal)0.0f << (qreal)0.0f << (qreal)0.0f; + QTest::newRow("rect") + << (qreal)1.0f << (qreal)-20.5f << (qreal)100.0f << (qreal)63.75f; +} +void tst_QMatrixNxN::mapRect() +{ + QFETCH(qreal, x); + QFETCH(qreal, y); + QFETCH(qreal, width); + QFETCH(qreal, height); + + QRectF rect(x, y, width, height); + QRect recti(qRound(x), qRound(y), qRound(width), qRound(height)); + + QMatrix4x4 m1; + QVERIFY(m1.mapRect(rect) == rect); + QVERIFY(m1.mapRect(recti) == recti); + + QMatrix4x4 m2; + m2.translate(-100.5f, 64.0f); + QRectF translated = rect.translated(-100.5f, 64.0f); + QRect translatedi = QRect(qRound(recti.x() - 100.5f), recti.y() + 64, + recti.width(), recti.height()); + QVERIFY(m2.mapRect(rect) == translated); + QVERIFY(m2.mapRect(recti) == translatedi); + + QMatrix4x4 m3; + m3.scale(-100.5f, 64.0f); + qreal scalex = x * -100.5f; + qreal scaley = y * 64.0f; + qreal scalewid = width * -100.5f; + qreal scaleht = height * 64.0f; + if (scalewid < 0.0f) { + scalewid = -scalewid; + scalex -= scalewid; + } + if (scaleht < 0.0f) { + scaleht = -scaleht; + scaley -= scaleht; + } + QRectF scaled(scalex, scaley, scalewid, scaleht); + QVERIFY(m3.mapRect(rect) == scaled); + scalex = recti.x() * -100.5f; + scaley = recti.y() * 64.0f; + scalewid = recti.width() * -100.5f; + scaleht = recti.height() * 64.0f; + if (scalewid < 0.0f) { + scalewid = -scalewid; + scalex -= scalewid; + } + if (scaleht < 0.0f) { + scaleht = -scaleht; + scaley -= scaleht; + } + QRect scaledi(qRound(scalex), qRound(scaley), + qRound(scalewid), qRound(scaleht)); + QVERIFY(m3.mapRect(recti) == scaledi); + + QMatrix4x4 m4; + m4.translate(-100.5f, 64.0f); + m4.scale(-2.5f, 4.0f); + qreal transx1 = x * -2.5f - 100.5f; + qreal transy1 = y * 4.0f + 64.0f; + qreal transx2 = (x + width) * -2.5f - 100.5f; + qreal transy2 = (y + height) * 4.0f + 64.0f; + if (transx1 > transx2) + qSwap(transx1, transx2); + if (transy1 > transy2) + qSwap(transy1, transy2); + QRectF trans(transx1, transy1, transx2 - transx1, transy2 - transy1); + QVERIFY(m4.mapRect(rect) == trans); + transx1 = recti.x() * -2.5f - 100.5f; + transy1 = recti.y() * 4.0f + 64.0f; + transx2 = (recti.x() + recti.width()) * -2.5f - 100.5f; + transy2 = (recti.y() + recti.height()) * 4.0f + 64.0f; + if (transx1 > transx2) + qSwap(transx1, transx2); + if (transy1 > transy2) + qSwap(transy1, transy2); + QRect transi(qRound(transx1), qRound(transy1), + qRound(transx2) - qRound(transx1), + qRound(transy2) - qRound(transy1)); + QVERIFY(m4.mapRect(recti) == transi); + + m4.rotate(45.0f, 0.0f, 0.0f, 1.0f); + + QTransform t4; + t4.translate(-100.5f, 64.0f); + t4.scale(-2.5f, 4.0f); + t4.rotate(45.0f); + QRectF mr = m4.mapRect(rect); + QRectF tr = t4.mapRect(rect); + QVERIFY(fuzzyCompare(mr.x(), tr.x())); + QVERIFY(fuzzyCompare(mr.y(), tr.y())); + QVERIFY(fuzzyCompare(mr.width(), tr.width())); + QVERIFY(fuzzyCompare(mr.height(), tr.height())); + + QRect mri = m4.mapRect(recti); + QRect tri = t4.mapRect(recti); + QVERIFY(mri == tri); +} + +class tst_QMatrixNxN4x4Properties : public QObject +{ + Q_OBJECT + Q_PROPERTY(QMatrix4x4 matrix READ matrix WRITE setMatrix) +public: + tst_QMatrixNxN4x4Properties(QObject *parent = 0) : QObject(parent) {} + + QMatrix4x4 matrix() const { return m; } + void setMatrix(const QMatrix4x4& value) { m = value; } + +private: + QMatrix4x4 m; +}; + +// Test getting and setting matrix properties via the metaobject system. +void tst_QMatrixNxN::properties() +{ + tst_QMatrixNxN4x4Properties obj; + + QMatrix4x4 m1(uniqueValues4); + obj.setMatrix(m1); + + QMatrix4x4 m2 = qVariantValue<QMatrix4x4>(obj.property("matrix")); + QVERIFY(isSame(m2, uniqueValues4)); + + QMatrix4x4 m3(transposedValues4); + obj.setProperty("matrix", qVariantFromValue(m3)); + + m2 = qVariantValue<QMatrix4x4>(obj.property("matrix")); + QVERIFY(isSame(m2, transposedValues4)); +} + +void tst_QMatrixNxN::metaTypes() +{ + QVERIFY(QMetaType::type("QMatrix4x4") == QMetaType::QMatrix4x4); + + QCOMPARE(QByteArray(QMetaType::typeName(QMetaType::QMatrix4x4)), + QByteArray("QMatrix4x4")); + + QVERIFY(QMetaType::isRegistered(QMetaType::QMatrix4x4)); + + QVERIFY(qMetaTypeId<QMatrix4x4>() == QMetaType::QMatrix4x4); +} + +QTEST_APPLESS_MAIN(tst_QMatrixNxN) + +#include "tst_qmatrixnxn.moc" |