/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qdoublematrix4x4_p.h" #include //#include #include #include QT_BEGIN_NAMESPACE static const double inv_dist_to_plane = 1.0 / 1024.0; QDoubleMatrix4x4::QDoubleMatrix4x4(const double *values) { for (int row = 0; row < 4; ++row) for (int col = 0; col < 4; ++col) m[col][row] = values[row * 4 + col]; flagBits = General; } QDoubleMatrix4x4::QDoubleMatrix4x4(const double *values, int cols, int rows) { for (int col = 0; col < 4; ++col) { for (int row = 0; row < 4; ++row) { if (col < cols && row < rows) m[col][row] = values[col * rows + row]; else if (col == row) m[col][row] = 1.0; else m[col][row] = 0.0; } } flagBits = General; } static inline double matrixDet2(const double m[4][4], int col0, int col1, int row0, int row1) { return m[col0][row0] * m[col1][row1] - m[col0][row1] * m[col1][row0]; } static inline double matrixDet3 (const double m[4][4], int col0, int col1, int col2, int row0, int row1, int row2) { return m[col0][row0] * matrixDet2(m, col1, col2, row1, row2) - m[col1][row0] * matrixDet2(m, col0, col2, row1, row2) + m[col2][row0] * matrixDet2(m, col0, col1, row1, row2); } static inline double matrixDet4(const double m[4][4]) { double det; det = m[0][0] * matrixDet3(m, 1, 2, 3, 1, 2, 3); det -= m[1][0] * matrixDet3(m, 0, 2, 3, 1, 2, 3); det += m[2][0] * matrixDet3(m, 0, 1, 3, 1, 2, 3); det -= m[3][0] * matrixDet3(m, 0, 1, 2, 1, 2, 3); return det; } double QDoubleMatrix4x4::determinant() const { if ((flagBits & ~(Translation | Rotation2D | Rotation)) == Identity) return 1.0; if (flagBits < Rotation2D) return m[0][0] * m[1][1] * m[2][2]; // Translation | Scale if (flagBits < Perspective) return matrixDet3(m, 0, 1, 2, 0, 1, 2); return matrixDet4(m); } QDoubleMatrix4x4 QDoubleMatrix4x4::inverted(bool *invertible) const { // Handle some of the easy cases first. if (flagBits == Identity) { if (invertible) *invertible = true; return QDoubleMatrix4x4(); } else if (flagBits == Translation) { QDoubleMatrix4x4 inv; inv.m[3][0] = -m[3][0]; inv.m[3][1] = -m[3][1]; inv.m[3][2] = -m[3][2]; inv.flagBits = Translation; if (invertible) *invertible = true; return inv; } else if (flagBits < Rotation2D) { // Translation | Scale if (m[0][0] == 0 || m[1][1] == 0 || m[2][2] == 0) { if (invertible) *invertible = false; return QDoubleMatrix4x4(); } QDoubleMatrix4x4 inv; inv.m[0][0] = 1.0 / m[0][0]; inv.m[1][1] = 1.0 / m[1][1]; inv.m[2][2] = 1.0 / m[2][2]; inv.m[3][0] = -m[3][0] * inv.m[0][0]; inv.m[3][1] = -m[3][1] * inv.m[1][1]; inv.m[3][2] = -m[3][2] * inv.m[2][2]; inv.flagBits = flagBits; if (invertible) *invertible = true; return inv; } else if ((flagBits & ~(Translation | Rotation2D | Rotation)) == Identity) { if (invertible) *invertible = true; return orthonormalInverse(); } else if (flagBits < Perspective) { QDoubleMatrix4x4 inv(1); // The "1" says to not load the identity. double det = matrixDet3(m, 0, 1, 2, 0, 1, 2); if (det == 0.0) { if (invertible) *invertible = false; return QDoubleMatrix4x4(); } det = 1.0 / det; inv.m[0][0] = matrixDet2(m, 1, 2, 1, 2) * det; inv.m[0][1] = -matrixDet2(m, 0, 2, 1, 2) * det; inv.m[0][2] = matrixDet2(m, 0, 1, 1, 2) * det; inv.m[0][3] = 0; inv.m[1][0] = -matrixDet2(m, 1, 2, 0, 2) * det; inv.m[1][1] = matrixDet2(m, 0, 2, 0, 2) * det; inv.m[1][2] = -matrixDet2(m, 0, 1, 0, 2) * det; inv.m[1][3] = 0; inv.m[2][0] = matrixDet2(m, 1, 2, 0, 1) * det; inv.m[2][1] = -matrixDet2(m, 0, 2, 0, 1) * det; inv.m[2][2] = matrixDet2(m, 0, 1, 0, 1) * det; inv.m[2][3] = 0; inv.m[3][0] = -inv.m[0][0] * m[3][0] - inv.m[1][0] * m[3][1] - inv.m[2][0] * m[3][2]; inv.m[3][1] = -inv.m[0][1] * m[3][0] - inv.m[1][1] * m[3][1] - inv.m[2][1] * m[3][2]; inv.m[3][2] = -inv.m[0][2] * m[3][0] - inv.m[1][2] * m[3][1] - inv.m[2][2] * m[3][2]; inv.m[3][3] = 1; inv.flagBits = flagBits; if (invertible) *invertible = true; return inv; } QDoubleMatrix4x4 inv(1); // The "1" says to not load the identity. double det = matrixDet4(m); if (det == 0.0) { if (invertible) *invertible = false; return QDoubleMatrix4x4(); } det = 1.0 / det; inv.m[0][0] = matrixDet3(m, 1, 2, 3, 1, 2, 3) * det; inv.m[0][1] = -matrixDet3(m, 0, 2, 3, 1, 2, 3) * det; inv.m[0][2] = matrixDet3(m, 0, 1, 3, 1, 2, 3) * det; inv.m[0][3] = -matrixDet3(m, 0, 1, 2, 1, 2, 3) * det; inv.m[1][0] = -matrixDet3(m, 1, 2, 3, 0, 2, 3) * det; inv.m[1][1] = matrixDet3(m, 0, 2, 3, 0, 2, 3) * det; inv.m[1][2] = -matrixDet3(m, 0, 1, 3, 0, 2, 3) * det; inv.m[1][3] = matrixDet3(m, 0, 1, 2, 0, 2, 3) * det; inv.m[2][0] = matrixDet3(m, 1, 2, 3, 0, 1, 3) * det; inv.m[2][1] = -matrixDet3(m, 0, 2, 3, 0, 1, 3) * det; inv.m[2][2] = matrixDet3(m, 0, 1, 3, 0, 1, 3) * det; inv.m[2][3] = -matrixDet3(m, 0, 1, 2, 0, 1, 3) * det; inv.m[3][0] = -matrixDet3(m, 1, 2, 3, 0, 1, 2) * det; inv.m[3][1] = matrixDet3(m, 0, 2, 3, 0, 1, 2) * det; inv.m[3][2] = -matrixDet3(m, 0, 1, 3, 0, 1, 2) * det; inv.m[3][3] = matrixDet3(m, 0, 1, 2, 0, 1, 2) * det; inv.flagBits = flagBits; if (invertible) *invertible = true; return inv; } QDoubleMatrix4x4 QDoubleMatrix4x4::transposed() const { QDoubleMatrix4x4 result(1); // The "1" says to not load the identity. for (int row = 0; row < 4; ++row) { for (int col = 0; col < 4; ++col) { result.m[col][row] = m[row][col]; } } // When a translation is transposed, it becomes a perspective transformation. result.flagBits = (flagBits & Translation ? General : flagBits); return result; } QDoubleMatrix4x4& QDoubleMatrix4x4::operator/=(double divisor) { m[0][0] /= divisor; m[0][1] /= divisor; m[0][2] /= divisor; m[0][3] /= divisor; m[1][0] /= divisor; m[1][1] /= divisor; m[1][2] /= divisor; m[1][3] /= divisor; m[2][0] /= divisor; m[2][1] /= divisor; m[2][2] /= divisor; m[2][3] /= divisor; m[3][0] /= divisor; m[3][1] /= divisor; m[3][2] /= divisor; m[3][3] /= divisor; flagBits = General; return *this; } QDoubleMatrix4x4 operator/(const QDoubleMatrix4x4& matrix, double divisor) { QDoubleMatrix4x4 m(1); // The "1" says to not load the identity. m.m[0][0] = matrix.m[0][0] / divisor; m.m[0][1] = matrix.m[0][1] / divisor; m.m[0][2] = matrix.m[0][2] / divisor; m.m[0][3] = matrix.m[0][3] / divisor; m.m[1][0] = matrix.m[1][0] / divisor; m.m[1][1] = matrix.m[1][1] / divisor; m.m[1][2] = matrix.m[1][2] / divisor; m.m[1][3] = matrix.m[1][3] / divisor; m.m[2][0] = matrix.m[2][0] / divisor; m.m[2][1] = matrix.m[2][1] / divisor; m.m[2][2] = matrix.m[2][2] / divisor; m.m[2][3] = matrix.m[2][3] / divisor; m.m[3][0] = matrix.m[3][0] / divisor; m.m[3][1] = matrix.m[3][1] / divisor; m.m[3][2] = matrix.m[3][2] / divisor; m.m[3][3] = matrix.m[3][3] / divisor; m.flagBits = QDoubleMatrix4x4::General; return m; } void QDoubleMatrix4x4::scale(const QDoubleVector3D& vector) { double vx = vector.x(); double vy = vector.y(); double vz = vector.z(); if (flagBits < Scale) { m[0][0] = vx; m[1][1] = vy; m[2][2] = vz; } else if (flagBits < Rotation2D) { m[0][0] *= vx; m[1][1] *= vy; m[2][2] *= vz; } else if (flagBits < Rotation) { m[0][0] *= vx; m[0][1] *= vx; m[1][0] *= vy; m[1][1] *= vy; m[2][2] *= vz; } else { m[0][0] *= vx; m[0][1] *= vx; m[0][2] *= vx; m[0][3] *= vx; m[1][0] *= vy; m[1][1] *= vy; m[1][2] *= vy; m[1][3] *= vy; m[2][0] *= vz; m[2][1] *= vz; m[2][2] *= vz; m[2][3] *= vz; } flagBits |= Scale; } void QDoubleMatrix4x4::scale(double x, double y) { if (flagBits < Scale) { m[0][0] = x; m[1][1] = y; } else if (flagBits < Rotation2D) { m[0][0] *= x; m[1][1] *= y; } else if (flagBits < Rotation) { m[0][0] *= x; m[0][1] *= x; m[1][0] *= y; m[1][1] *= y; } else { m[0][0] *= x; m[0][1] *= x; m[0][2] *= x; m[0][3] *= x; m[1][0] *= y; m[1][1] *= y; m[1][2] *= y; m[1][3] *= y; } flagBits |= Scale; } void QDoubleMatrix4x4::scale(double x, double y, double z) { if (flagBits < Scale) { m[0][0] = x; m[1][1] = y; m[2][2] = z; } else if (flagBits < Rotation2D) { m[0][0] *= x; m[1][1] *= y; m[2][2] *= z; } else if (flagBits < Rotation) { m[0][0] *= x; m[0][1] *= x; m[1][0] *= y; m[1][1] *= y; m[2][2] *= z; } else { m[0][0] *= x; m[0][1] *= x; m[0][2] *= x; m[0][3] *= x; m[1][0] *= y; m[1][1] *= y; m[1][2] *= y; m[1][3] *= y; m[2][0] *= z; m[2][1] *= z; m[2][2] *= z; m[2][3] *= z; } flagBits |= Scale; } void QDoubleMatrix4x4::scale(double factor) { if (flagBits < Scale) { m[0][0] = factor; m[1][1] = factor; m[2][2] = factor; } else if (flagBits < Rotation2D) { m[0][0] *= factor; m[1][1] *= factor; m[2][2] *= factor; } else if (flagBits < Rotation) { m[0][0] *= factor; m[0][1] *= factor; m[1][0] *= factor; m[1][1] *= factor; m[2][2] *= factor; } else { m[0][0] *= factor; m[0][1] *= factor; m[0][2] *= factor; m[0][3] *= factor; m[1][0] *= factor; m[1][1] *= factor; m[1][2] *= factor; m[1][3] *= factor; m[2][0] *= factor; m[2][1] *= factor; m[2][2] *= factor; m[2][3] *= factor; } flagBits |= Scale; } void QDoubleMatrix4x4::translate(const QDoubleVector3D& vector) { double vx = vector.x(); double vy = vector.y(); double vz = vector.z(); if (flagBits == Identity) { m[3][0] = vx; m[3][1] = vy; m[3][2] = vz; } else if (flagBits == Translation) { m[3][0] += vx; m[3][1] += vy; m[3][2] += vz; } else if (flagBits == Scale) { m[3][0] = m[0][0] * vx; m[3][1] = m[1][1] * vy; m[3][2] = m[2][2] * vz; } else if (flagBits == (Translation | Scale)) { m[3][0] += m[0][0] * vx; m[3][1] += m[1][1] * vy; m[3][2] += m[2][2] * vz; } else if (flagBits < Rotation) { m[3][0] += m[0][0] * vx + m[1][0] * vy; m[3][1] += m[0][1] * vx + m[1][1] * vy; m[3][2] += m[2][2] * vz; } else { m[3][0] += m[0][0] * vx + m[1][0] * vy + m[2][0] * vz; m[3][1] += m[0][1] * vx + m[1][1] * vy + m[2][1] * vz; m[3][2] += m[0][2] * vx + m[1][2] * vy + m[2][2] * vz; m[3][3] += m[0][3] * vx + m[1][3] * vy + m[2][3] * vz; } flagBits |= Translation; } void QDoubleMatrix4x4::translate(double x, double y) { if (flagBits == Identity) { m[3][0] = x; m[3][1] = y; } else if (flagBits == Translation) { m[3][0] += x; m[3][1] += y; } else if (flagBits == Scale) { m[3][0] = m[0][0] * x; m[3][1] = m[1][1] * y; } else if (flagBits == (Translation | Scale)) { m[3][0] += m[0][0] * x; m[3][1] += m[1][1] * y; } else if (flagBits < Rotation) { m[3][0] += m[0][0] * x + m[1][0] * y; m[3][1] += m[0][1] * x + m[1][1] * y; } else { m[3][0] += m[0][0] * x + m[1][0] * y; m[3][1] += m[0][1] * x + m[1][1] * y; m[3][2] += m[0][2] * x + m[1][2] * y; m[3][3] += m[0][3] * x + m[1][3] * y; } flagBits |= Translation; } void QDoubleMatrix4x4::translate(double x, double y, double z) { if (flagBits == Identity) { m[3][0] = x; m[3][1] = y; m[3][2] = z; } else if (flagBits == Translation) { m[3][0] += x; m[3][1] += y; m[3][2] += z; } else if (flagBits == Scale) { m[3][0] = m[0][0] * x; m[3][1] = m[1][1] * y; m[3][2] = m[2][2] * z; } else if (flagBits == (Translation | Scale)) { m[3][0] += m[0][0] * x; m[3][1] += m[1][1] * y; m[3][2] += m[2][2] * z; } else if (flagBits < Rotation) { m[3][0] += m[0][0] * x + m[1][0] * y; m[3][1] += m[0][1] * x + m[1][1] * y; m[3][2] += m[2][2] * z; } else { m[3][0] += m[0][0] * x + m[1][0] * y + m[2][0] * z; m[3][1] += m[0][1] * x + m[1][1] * y + m[2][1] * z; m[3][2] += m[0][2] * x + m[1][2] * y + m[2][2] * z; m[3][3] += m[0][3] * x + m[1][3] * y + m[2][3] * z; } flagBits |= Translation; } void QDoubleMatrix4x4::rotate(double angle, const QDoubleVector3D& vector) { rotate(angle, vector.x(), vector.y(), vector.z()); } void QDoubleMatrix4x4::rotate(double angle, double x, double y, double z) { if (angle == 0.0) return; double c, s; if (angle == 90.0 || angle == -270.0) { s = 1.0; c = 0.0; } else if (angle == -90.0 || angle == 270.0) { s = -1.0; c = 0.0; } else if (angle == 180.0 || angle == -180.0) { s = 0.0; c = -1.0; } else { double a = qDegreesToRadians(angle); c = std::cos(a); s = std::sin(a); } if (x == 0.0) { if (y == 0.0) { if (z != 0.0) { // Rotate around the Z axis. if (z < 0) s = -s; double tmp; m[0][0] = (tmp = m[0][0]) * c + m[1][0] * s; m[1][0] = m[1][0] * c - tmp * s; m[0][1] = (tmp = m[0][1]) * c + m[1][1] * s; m[1][1] = m[1][1] * c - tmp * s; m[0][2] = (tmp = m[0][2]) * c + m[1][2] * s; m[1][2] = m[1][2] * c - tmp * s; m[0][3] = (tmp = m[0][3]) * c + m[1][3] * s; m[1][3] = m[1][3] * c - tmp * s; flagBits |= Rotation2D; return; } } else if (z == 0.0) { // Rotate around the Y axis. if (y < 0) s = -s; double tmp; m[2][0] = (tmp = m[2][0]) * c + m[0][0] * s; m[0][0] = m[0][0] * c - tmp * s; m[2][1] = (tmp = m[2][1]) * c + m[0][1] * s; m[0][1] = m[0][1] * c - tmp * s; m[2][2] = (tmp = m[2][2]) * c + m[0][2] * s; m[0][2] = m[0][2] * c - tmp * s; m[2][3] = (tmp = m[2][3]) * c + m[0][3] * s; m[0][3] = m[0][3] * c - tmp * s; flagBits |= Rotation; return; } } else if (y == 0.0 && z == 0.0) { // Rotate around the X axis. if (x < 0) s = -s; double tmp; m[1][0] = (tmp = m[1][0]) * c + m[2][0] * s; m[2][0] = m[2][0] * c - tmp * s; m[1][1] = (tmp = m[1][1]) * c + m[2][1] * s; m[2][1] = m[2][1] * c - tmp * s; m[1][2] = (tmp = m[1][2]) * c + m[2][2] * s; m[2][2] = m[2][2] * c - tmp * s; m[1][3] = (tmp = m[1][3]) * c + m[2][3] * s; m[2][3] = m[2][3] * c - tmp * s; flagBits |= Rotation; return; } double len = double(x) * double(x) + double(y) * double(y) + double(z) * double(z); if (!qFuzzyCompare(len, 1.0) && !qFuzzyIsNull(len)) { len = std::sqrt(len); x = double(double(x) / len); y = double(double(y) / len); z = double(double(z) / len); } double ic = 1.0 - c; QDoubleMatrix4x4 rot(1); // The "1" says to not load the identity. rot.m[0][0] = x * x * ic + c; rot.m[1][0] = x * y * ic - z * s; rot.m[2][0] = x * z * ic + y * s; rot.m[3][0] = 0.0; rot.m[0][1] = y * x * ic + z * s; rot.m[1][1] = y * y * ic + c; rot.m[2][1] = y * z * ic - x * s; rot.m[3][1] = 0.0; rot.m[0][2] = x * z * ic - y * s; rot.m[1][2] = y * z * ic + x * s; rot.m[2][2] = z * z * ic + c; rot.m[3][2] = 0.0; rot.m[0][3] = 0.0; rot.m[1][3] = 0.0; rot.m[2][3] = 0.0; rot.m[3][3] = 1.0; rot.flagBits = Rotation; *this *= rot; } void QDoubleMatrix4x4::projectedRotate(double angle, double x, double y, double z) { // Used by QGraphicsRotation::applyTo() to perform a rotation // and projection back to 2D in a single step. if (angle == 0.0) return; double c, s; if (angle == 90.0 || angle == -270.0) { s = 1.0; c = 0.0; } else if (angle == -90.0 || angle == 270.0) { s = -1.0; c = 0.0; } else if (angle == 180.0 || angle == -180.0) { s = 0.0; c = -1.0; } else { double a = qDegreesToRadians(angle); c = std::cos(a); s = std::sin(a); } if (x == 0.0) { if (y == 0.0) { if (z != 0.0) { // Rotate around the Z axis. if (z < 0) s = -s; double tmp; m[0][0] = (tmp = m[0][0]) * c + m[1][0] * s; m[1][0] = m[1][0] * c - tmp * s; m[0][1] = (tmp = m[0][1]) * c + m[1][1] * s; m[1][1] = m[1][1] * c - tmp * s; m[0][2] = (tmp = m[0][2]) * c + m[1][2] * s; m[1][2] = m[1][2] * c - tmp * s; m[0][3] = (tmp = m[0][3]) * c + m[1][3] * s; m[1][3] = m[1][3] * c - tmp * s; flagBits |= Rotation2D; return; } } else if (z == 0.0) { // Rotate around the Y axis. if (y < 0) s = -s; m[0][0] = m[0][0] * c + m[3][0] * s * inv_dist_to_plane; m[0][1] = m[0][1] * c + m[3][1] * s * inv_dist_to_plane; m[0][2] = m[0][2] * c + m[3][2] * s * inv_dist_to_plane; m[0][3] = m[0][3] * c + m[3][3] * s * inv_dist_to_plane; flagBits = General; return; } } else if (y == 0.0 && z == 0.0) { // Rotate around the X axis. if (x < 0) s = -s; m[1][0] = m[1][0] * c - m[3][0] * s * inv_dist_to_plane; m[1][1] = m[1][1] * c - m[3][1] * s * inv_dist_to_plane; m[1][2] = m[1][2] * c - m[3][2] * s * inv_dist_to_plane; m[1][3] = m[1][3] * c - m[3][3] * s * inv_dist_to_plane; flagBits = General; return; } double len = double(x) * double(x) + double(y) * double(y) + double(z) * double(z); if (!qFuzzyCompare(len, 1.0) && !qFuzzyIsNull(len)) { len = std::sqrt(len); x = double(double(x) / len); y = double(double(y) / len); z = double(double(z) / len); } double ic = 1.0 - c; QDoubleMatrix4x4 rot(1); // The "1" says to not load the identity. rot.m[0][0] = x * x * ic + c; rot.m[1][0] = x * y * ic - z * s; rot.m[2][0] = 0.0; rot.m[3][0] = 0.0; rot.m[0][1] = y * x * ic + z * s; rot.m[1][1] = y * y * ic + c; rot.m[2][1] = 0.0; rot.m[3][1] = 0.0; rot.m[0][2] = 0.0; rot.m[1][2] = 0.0; rot.m[2][2] = 1.0; rot.m[3][2] = 0.0; rot.m[0][3] = (x * z * ic - y * s) * -inv_dist_to_plane; rot.m[1][3] = (y * z * ic + x * s) * -inv_dist_to_plane; rot.m[2][3] = 0.0; rot.m[3][3] = 1.0; rot.flagBits = General; *this *= rot; } void QDoubleMatrix4x4::ortho(const QRect& rect) { // Note: rect.right() and rect.bottom() subtract 1 in QRect, // which gives the location of a pixel within the rectangle, // instead of the extent of the rectangle. We want the extent. // QRectF expresses the extent properly. ortho(rect.x(), rect.x() + rect.width(), rect.y() + rect.height(), rect.y(), -1.0, 1.0); } void QDoubleMatrix4x4::ortho(const QRectF& rect) { ortho(rect.left(), rect.right(), rect.bottom(), rect.top(), -1.0, 1.0); } void QDoubleMatrix4x4::ortho(double left, double right, double bottom, double top, double nearPlane, double farPlane) { // Bail out if the projection volume is zero-sized. if (left == right || bottom == top || nearPlane == farPlane) return; // Construct the projection. double width = right - left; double invheight = top - bottom; double clip = farPlane - nearPlane; QDoubleMatrix4x4 m(1); m.m[0][0] = 2.0 / width; m.m[1][0] = 0.0; m.m[2][0] = 0.0; m.m[3][0] = -(left + right) / width; m.m[0][1] = 0.0; m.m[1][1] = 2.0 / invheight; m.m[2][1] = 0.0; m.m[3][1] = -(top + bottom) / invheight; m.m[0][2] = 0.0; m.m[1][2] = 0.0; m.m[2][2] = -2.0 / clip; m.m[3][2] = -(nearPlane + farPlane) / clip; m.m[0][3] = 0.0; m.m[1][3] = 0.0; m.m[2][3] = 0.0; m.m[3][3] = 1.0; m.flagBits = Translation | Scale; // Apply the projection. *this *= m; } void QDoubleMatrix4x4::frustum(double left, double right, double bottom, double top, double nearPlane, double farPlane) { // Bail out if the projection volume is zero-sized. if (left == right || bottom == top || nearPlane == farPlane) return; // Construct the projection. QDoubleMatrix4x4 m(1); double width = right - left; double invheight = top - bottom; double clip = farPlane - nearPlane; m.m[0][0] = 2.0 * nearPlane / width; m.m[1][0] = 0.0; m.m[2][0] = (left + right) / width; m.m[3][0] = 0.0; m.m[0][1] = 0.0; m.m[1][1] = 2.0 * nearPlane / invheight; m.m[2][1] = (top + bottom) / invheight; m.m[3][1] = 0.0; m.m[0][2] = 0.0; m.m[1][2] = 0.0; m.m[2][2] = -(nearPlane + farPlane) / clip; m.m[3][2] = -2.0 * nearPlane * farPlane / clip; m.m[0][3] = 0.0; m.m[1][3] = 0.0; m.m[2][3] = -1.0; m.m[3][3] = 0.0; m.flagBits = General; // Apply the projection. *this *= m; } void QDoubleMatrix4x4::perspective(double verticalAngle, double aspectRatio, double nearPlane, double farPlane) { // Bail out if the projection volume is zero-sized. if (nearPlane == farPlane || aspectRatio == 0.0) return; // Construct the projection. QDoubleMatrix4x4 m(1); double radians = qDegreesToRadians(verticalAngle / 2.0); double sine = std::sin(radians); if (sine == 0.0) return; double cotan = std::cos(radians) / sine; double clip = farPlane - nearPlane; m.m[0][0] = cotan / aspectRatio; m.m[1][0] = 0.0; m.m[2][0] = 0.0; m.m[3][0] = 0.0; m.m[0][1] = 0.0; m.m[1][1] = cotan; m.m[2][1] = 0.0; m.m[3][1] = 0.0; m.m[0][2] = 0.0; m.m[1][2] = 0.0; m.m[2][2] = -(nearPlane + farPlane) / clip; m.m[3][2] = -(2.0 * nearPlane * farPlane) / clip; m.m[0][3] = 0.0; m.m[1][3] = 0.0; m.m[2][3] = -1.0; m.m[3][3] = 0.0; m.flagBits = General; // Apply the projection. *this *= m; } void QDoubleMatrix4x4::lookAt(const QDoubleVector3D& eye, const QDoubleVector3D& center, const QDoubleVector3D& up) { QDoubleVector3D forward = center - eye; if (qFuzzyIsNull(forward.x()) && qFuzzyIsNull(forward.y()) && qFuzzyIsNull(forward.z())) return; forward.normalize(); QDoubleVector3D side = QDoubleVector3D::crossProduct(forward, up).normalized(); QDoubleVector3D upVector = QDoubleVector3D::crossProduct(side, forward); QDoubleMatrix4x4 m(1); m.m[0][0] = side.x(); m.m[1][0] = side.y(); m.m[2][0] = side.z(); m.m[3][0] = 0.0; m.m[0][1] = upVector.x(); m.m[1][1] = upVector.y(); m.m[2][1] = upVector.z(); m.m[3][1] = 0.0; m.m[0][2] = -forward.x(); m.m[1][2] = -forward.y(); m.m[2][2] = -forward.z(); m.m[3][2] = 0.0; m.m[0][3] = 0.0; m.m[1][3] = 0.0; m.m[2][3] = 0.0; m.m[3][3] = 1.0; m.flagBits = Rotation; *this *= m; translate(-eye); } void QDoubleMatrix4x4::viewport(double left, double bottom, double width, double height, double nearPlane, double farPlane) { const double w2 = width / 2.0; const double h2 = height / 2.0; QDoubleMatrix4x4 m(1); m.m[0][0] = w2; m.m[1][0] = 0.0; m.m[2][0] = 0.0; m.m[3][0] = left + w2; m.m[0][1] = 0.0; m.m[1][1] = h2; m.m[2][1] = 0.0; m.m[3][1] = bottom + h2; m.m[0][2] = 0.0; m.m[1][2] = 0.0; m.m[2][2] = (farPlane - nearPlane) / 2.0; m.m[3][2] = (nearPlane + farPlane) / 2.0; m.m[0][3] = 0.0; m.m[1][3] = 0.0; m.m[2][3] = 0.0; m.m[3][3] = 1.0; m.flagBits = General; *this *= m; } void QDoubleMatrix4x4::flipCoordinates() { // Multiplying the y and z coordinates with -1 does NOT flip between right-handed and // left-handed coordinate systems, it just rotates 180 degrees around the x axis, so // I'm deprecating this function. if (flagBits < Rotation2D) { // Translation | Scale m[1][1] = -m[1][1]; m[2][2] = -m[2][2]; } else { m[1][0] = -m[1][0]; m[1][1] = -m[1][1]; m[1][2] = -m[1][2]; m[1][3] = -m[1][3]; m[2][0] = -m[2][0]; m[2][1] = -m[2][1]; m[2][2] = -m[2][2]; m[2][3] = -m[2][3]; } flagBits |= Scale; } void QDoubleMatrix4x4::copyDataTo(double *values) const { for (int row = 0; row < 4; ++row) for (int col = 0; col < 4; ++col) values[row * 4 + col] = double(m[col][row]); } QRect QDoubleMatrix4x4::mapRect(const QRect& rect) const { if (flagBits < Scale) { // Translation return QRect(qRound(rect.x() + m[3][0]), qRound(rect.y() + m[3][1]), rect.width(), rect.height()); } else if (flagBits < Rotation2D) { // Translation | Scale double x = rect.x() * m[0][0] + m[3][0]; double y = rect.y() * m[1][1] + m[3][1]; double w = rect.width() * m[0][0]; double h = rect.height() * m[1][1]; if (w < 0) { w = -w; x -= w; } if (h < 0) { h = -h; y -= h; } return QRect(qRound(x), qRound(y), qRound(w), qRound(h)); } QPoint tl = map(rect.topLeft()); QPoint tr = map(QPoint(rect.x() + rect.width(), rect.y())); QPoint bl = map(QPoint(rect.x(), rect.y() + rect.height())); QPoint br = map(QPoint(rect.x() + rect.width(), rect.y() + rect.height())); int xmin = qMin(qMin(tl.x(), tr.x()), qMin(bl.x(), br.x())); int xmax = qMax(qMax(tl.x(), tr.x()), qMax(bl.x(), br.x())); int ymin = qMin(qMin(tl.y(), tr.y()), qMin(bl.y(), br.y())); int ymax = qMax(qMax(tl.y(), tr.y()), qMax(bl.y(), br.y())); return QRect(xmin, ymin, xmax - xmin, ymax - ymin); } QRectF QDoubleMatrix4x4::mapRect(const QRectF& rect) const { if (flagBits < Scale) { // Translation return rect.translated(m[3][0], m[3][1]); } else if (flagBits < Rotation2D) { // Translation | Scale double x = rect.x() * m[0][0] + m[3][0]; double y = rect.y() * m[1][1] + m[3][1]; double w = rect.width() * m[0][0]; double h = rect.height() * m[1][1]; if (w < 0) { w = -w; x -= w; } if (h < 0) { h = -h; y -= h; } return QRectF(x, y, w, h); } QPointF tl = map(rect.topLeft()); QPointF tr = map(rect.topRight()); QPointF bl = map(rect.bottomLeft()); QPointF br = map(rect.bottomRight()); double xmin = qMin(qMin(tl.x(), tr.x()), qMin(bl.x(), br.x())); double xmax = qMax(qMax(tl.x(), tr.x()), qMax(bl.x(), br.x())); double ymin = qMin(qMin(tl.y(), tr.y()), qMin(bl.y(), br.y())); double ymax = qMax(qMax(tl.y(), tr.y()), qMax(bl.y(), br.y())); return QRectF(QPointF(xmin, ymin), QPointF(xmax, ymax)); } QDoubleMatrix4x4 QDoubleMatrix4x4::orthonormalInverse() const { QDoubleMatrix4x4 result(1); // The '1' says not to load identity result.m[0][0] = m[0][0]; result.m[1][0] = m[0][1]; result.m[2][0] = m[0][2]; result.m[0][1] = m[1][0]; result.m[1][1] = m[1][1]; result.m[2][1] = m[1][2]; result.m[0][2] = m[2][0]; result.m[1][2] = m[2][1]; result.m[2][2] = m[2][2]; result.m[0][3] = 0.0; result.m[1][3] = 0.0; result.m[2][3] = 0.0; result.m[3][0] = -(result.m[0][0] * m[3][0] + result.m[1][0] * m[3][1] + result.m[2][0] * m[3][2]); result.m[3][1] = -(result.m[0][1] * m[3][0] + result.m[1][1] * m[3][1] + result.m[2][1] * m[3][2]); result.m[3][2] = -(result.m[0][2] * m[3][0] + result.m[1][2] * m[3][1] + result.m[2][2] * m[3][2]); result.m[3][3] = 1.0; result.flagBits = flagBits; return result; } void QDoubleMatrix4x4::optimize() { // If the last row is not (0, 0, 0, 1), the matrix is not a special type. flagBits = General; if (m[0][3] != 0 || m[1][3] != 0 || m[2][3] != 0 || m[3][3] != 1) return; flagBits &= ~Perspective; // If the last column is (0, 0, 0, 1), then there is no translation. if (m[3][0] == 0 && m[3][1] == 0 && m[3][2] == 0) flagBits &= ~Translation; // If the two first elements of row 3 and column 3 are 0, then any rotation must be about Z. if (!m[0][2] && !m[1][2] && !m[2][0] && !m[2][1]) { flagBits &= ~Rotation; // If the six non-diagonal elements in the top left 3x3 matrix are 0, there is no rotation. if (!m[0][1] && !m[1][0]) { flagBits &= ~Rotation2D; // Check for identity. if (m[0][0] == 1 && m[1][1] == 1 && m[2][2] == 1) flagBits &= ~Scale; } else { // If the columns are orthonormal and form a right-handed system, then there is no scale. double det = matrixDet2(m, 0, 1, 0, 1); double lenX = m[0][0] * m[0][0] + m[0][1] * m[0][1]; double lenY = m[1][0] * m[1][0] + m[1][1] * m[1][1]; double lenZ = m[2][2]; if (qFuzzyCompare(det, 1.0) && qFuzzyCompare(lenX, 1.0) && qFuzzyCompare(lenY, 1.0) && qFuzzyCompare(lenZ, 1.0)) { flagBits &= ~Scale; } } } else { // If the columns are orthonormal and form a right-handed system, then there is no scale. double det = matrixDet3(m, 0, 1, 2, 0, 1, 2); double lenX = m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]; double lenY = m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]; double lenZ = m[2][0] * m[2][0] + m[2][1] * m[2][1] + m[2][2] * m[2][2]; if (qFuzzyCompare(det, 1.0) && qFuzzyCompare(lenX, 1.0) && qFuzzyCompare(lenY, 1.0) && qFuzzyCompare(lenZ, 1.0)) { flagBits &= ~Scale; } } } #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QDoubleMatrix4x4 &m) { QDebugStateSaver saver(dbg); // Create a string that represents the matrix type. QByteArray bits; if (m.flagBits == QDoubleMatrix4x4::Identity) { bits = "Identity"; } else if (m.flagBits == QDoubleMatrix4x4::General) { bits = "General"; } else { if ((m.flagBits & QDoubleMatrix4x4::Translation) != 0) bits += "Translation,"; if ((m.flagBits & QDoubleMatrix4x4::Scale) != 0) bits += "Scale,"; if ((m.flagBits & QDoubleMatrix4x4::Rotation2D) != 0) bits += "Rotation2D,"; if ((m.flagBits & QDoubleMatrix4x4::Rotation) != 0) bits += "Rotation,"; if ((m.flagBits & QDoubleMatrix4x4::Perspective) != 0) bits += "Perspective,"; if (bits.size() > 0) bits = bits.left(bits.size() - 1); } // Output in row-major order because it is more human-readable. dbg.nospace() << "QDoubleMatrix4x4(type:" << bits.constData() << endl << qSetFieldWidth(10) << m(0, 0) << m(0, 1) << m(0, 2) << m(0, 3) << endl << m(1, 0) << m(1, 1) << m(1, 2) << m(1, 3) << endl << m(2, 0) << m(2, 1) << m(2, 2) << m(2, 3) << endl << m(3, 0) << m(3, 1) << m(3, 2) << m(3, 3) << endl << qSetFieldWidth(0) << ')'; return dbg; } #endif #ifndef QT_NO_DATASTREAM QDataStream &operator<<(QDataStream &stream, const QDoubleMatrix4x4 &matrix) { for (int row = 0; row < 4; ++row) for (int col = 0; col < 4; ++col) stream << matrix(row, col); return stream; } QDataStream &operator>>(QDataStream &stream, QDoubleMatrix4x4 &matrix) { double x; for (int row = 0; row < 4; ++row) { for (int col = 0; col < 4; ++col) { stream >> x; matrix(row, col) = x; } } matrix.optimize(); return stream; } #endif // QT_NO_DATASTREAM QT_END_NAMESPACE