From 93cd42ef606178261e12eeffc88cd4b7d029dbc3 Mon Sep 17 00:00:00 2001 From: Paolo Angelelli Date: Sun, 4 Dec 2016 20:43:26 +0100 Subject: Make QGeoProjectionWebMercator use a projection matrix This patch changes the way QGeoProjectionWebMercator implements its operations -to and -from screen coordinates. By using a double matrix 4x4 instead of only scaling and shifting this will make it possible to convert coordinates also in presence of bearing and tilting != 0 Change-Id: I6a74a9675a7ad397ec6ad32f4274f897aa7694b1 Reviewed-by: Alex Blasche --- src/location/maps/qgeoprojection.cpp | 141 ++++- src/location/maps/qgeoprojection_p.h | 40 +- src/positioning/positioning.pro | 4 +- src/positioning/qdoublematrix4x4.cpp | 1111 ++++++++++++++++++++++++++++++++++ src/positioning/qdoublematrix4x4_p.h | 946 +++++++++++++++++++++++++++++ src/positioning/qdoublevector2d_p.h | 8 + 6 files changed, 2214 insertions(+), 36 deletions(-) create mode 100644 src/positioning/qdoublematrix4x4.cpp create mode 100644 src/positioning/qdoublematrix4x4_p.h diff --git a/src/location/maps/qgeoprojection.cpp b/src/location/maps/qgeoprojection.cpp index ed85fc67..dc7e78f8 100644 --- a/src/location/maps/qgeoprojection.cpp +++ b/src/location/maps/qgeoprojection.cpp @@ -36,11 +36,14 @@ #include "qgeoprojection_p.h" #include +#include #include #include QT_BEGIN_NAMESPACE +static const double defaultTileSize = 256.0; + QGeoProjection::QGeoProjection() { @@ -62,14 +65,17 @@ QGeoProjectionWebMercator::QGeoProjectionWebMercator() m_minimumZoom(0), m_cameraCenterXMercator(0), m_cameraCenterYMercator(0), - m_cameraWidthMercator(1), - m_cameraHeightMercator(1), - m_1_cameraWidthMercator(0), - m_1_cameraHeightMercator(0), m_viewportWidth(1), m_viewportHeight(1), m_1_viewportWidth(0), - m_1_viewportHeight(0) + m_1_viewportHeight(0), + m_sideLength(256), + m_aperture(0.0), + m_nearPlane(0.0), + m_farPlane(0.0), + m_halfWidth(0.0), + m_halfHeight(0.0), + m_plane(QDoubleVector3D(0,0,0), QDoubleVector3D(0,0,1)) { } @@ -94,7 +100,7 @@ double QGeoProjectionWebMercator::minimumZoom() const // and the borders stays the same, but the meters per pixel change double QGeoProjectionWebMercator::maximumCenterLatitudeAtZoom(double zoomLevel) const { - double mapEdgeSize = std::pow(2.0, zoomLevel) * 256.0; + double mapEdgeSize = std::pow(2.0, zoomLevel) * defaultTileSize; // At init time weird things happen int clampedWindowHeight = (m_viewportHeight > mapEdgeSize) ? mapEdgeSize : m_viewportHeight; @@ -122,14 +128,14 @@ void QGeoProjectionWebMercator::setViewportSize(const QSize &size) m_viewportHeight = size.height(); m_1_viewportWidth = 1.0 / m_viewportWidth; m_1_viewportHeight = 1.0 / m_viewportHeight; - m_minimumZoom = std::log(qMax(m_viewportWidth, m_viewportHeight) / 256.0) / std::log(2.0); + m_minimumZoom = std::log(qMax(m_viewportWidth, m_viewportHeight) / defaultTileSize) / std::log(2.0); setupCamera(); } void QGeoProjectionWebMercator::setCameraData(const QGeoCameraData &cameraData) { m_cameraData = cameraData; - m_mapEdgeSize = std::pow(2.0, cameraData.zoomLevel()) * 256.0; + m_mapEdgeSize = std::pow(2.0, cameraData.zoomLevel()) * defaultTileSize; setupCamera(); } @@ -170,28 +176,30 @@ QDoubleVector2D QGeoProjectionWebMercator::unwrapMapProjection(const QDoubleVect QDoubleVector2D QGeoProjectionWebMercator::wrappedMapProjectionToItemPosition(const QDoubleVector2D &wrappedProjection) const { - // TODO: Support tilt/bearing through a projection matrix. - double x = ((wrappedProjection.x() - m_cameraCenterXMercator) * m_1_cameraWidthMercator + 0.5) * m_viewportWidth; - double y = ((wrappedProjection.y() - m_cameraCenterYMercator) * m_1_cameraHeightMercator + 0.5) * m_viewportHeight; - return QDoubleVector2D(x, y); + QDoubleVector3D pos = wrappedProjection * m_sideLength; + QDoubleVector2D res = (m_transformation * pos).toVector2D(); + res += QDoubleVector2D(1.0,1.0); + res *= 0.5; + res *= QDoubleVector2D(m_viewportWidth, m_viewportHeight); + return res; } QDoubleVector2D QGeoProjectionWebMercator::itemPositionToWrappedMapProjection(const QDoubleVector2D &itemPosition) const { - // TODO: Support tilt/bearing through an inverse projection matrix. - double x = itemPosition.x(); - x *= m_1_viewportWidth; - x -= 0.5; - x *= m_cameraWidthMercator; - x += m_cameraCenterXMercator; + QDoubleVector2D pos = itemPosition; + pos /= QDoubleVector2D(m_viewportWidth, m_viewportHeight); + pos *= 2.0; + pos -= QDoubleVector2D(1.0,1.0); + pos *= QDoubleVector2D(m_halfWidth, m_halfHeight); + + QDoubleVector3D p = m_centerNearPlane; + p -= m_up * pos.y(); + p -= m_side * pos.x(); - double y = itemPosition.y(); - y *= m_1_viewportHeight; - y -= 0.5; - y *= m_cameraHeightMercator; - y += m_cameraCenterYMercator; + QDoubleVector3D ray = p - m_eye; + ray.normalize(); - return QDoubleVector2D(x, y); + return (m_plane.lineIntersection(m_eye, ray) / m_sideLength).toVector2D(); } /* Default implementations */ @@ -207,7 +215,8 @@ QGeoCoordinate QGeoProjectionWebMercator::itemPositionToCoordinate(const QDouble QDoubleVector2D wrappedMapProjection = itemPositionToWrappedMapProjection(pos); // With rotation/tilting, a screen position might end up outside the projection space. - // TODO: test for it + if (!isProjectable(wrappedMapProjection)) + return QGeoCoordinate(); return mapProjectionToGeo(unwrapMapProjection(wrappedMapProjection)); } @@ -226,15 +235,87 @@ QDoubleVector2D QGeoProjectionWebMercator::coordinateToItemPosition(const QGeoCo return pos; } +bool QGeoProjectionWebMercator::isProjectable(const QDoubleVector2D &wrappedProjection) const +{ + QDoubleVector3D pos = wrappedProjection * m_sideLength; + + // TODO: add an offset to the eye + QDoubleVector3D p = m_eye - pos; + double dot = QDoubleVector3D::dotProduct(p , m_viewNormalized); + + if (dot < 0.0) // behind the eye + return false; + return true; +} + void QGeoProjectionWebMercator::setupCamera() { - m_cameraWidthMercator = m_viewportWidth / m_mapEdgeSize; - m_cameraHeightMercator = m_viewportHeight / m_mapEdgeSize; - m_1_cameraWidthMercator = 1.0 / m_cameraWidthMercator; - m_1_cameraHeightMercator = 1.0 / m_cameraHeightMercator; - QDoubleVector2D camCenterMercator = QWebMercator::coordToMercator(m_cameraData.center()); + QDoubleVector2D camCenterMercator = geoToMapProjection(m_cameraData.center()); m_cameraCenterXMercator = camCenterMercator.x(); m_cameraCenterYMercator = camCenterMercator.y(); + + int intZoomLevel = static_cast(std::floor(m_cameraData.zoomLevel())); + m_sideLength = (1 << intZoomLevel) * defaultTileSize; + m_center = camCenterMercator * m_sideLength; + + double f = 1.0 * qMin(m_viewportWidth, m_viewportHeight); + double z = std::pow(2.0, m_cameraData.zoomLevel() - intZoomLevel) * defaultTileSize; + double altitude = f / (2.0 * z) ; + + // calculate eye + // TODO: support field of view with apertureSize = tan(QLocationUtils::radians(m_cameraData.fieldOfView()) * 0.5); + double m_aperture = 1.0; //aperture(90 / 2) = 1 + + m_eye = m_center; + m_eye.setZ(altitude * defaultTileSize / m_aperture); + + m_view = m_eye - m_center; + QDoubleVector3D side = QDoubleVector3D::normal(m_view, QDoubleVector3D(0.0, 1.0, 0.0)); + m_up = QDoubleVector3D::normal(side, m_view); + + if (m_cameraData.bearing() > 0.0) { + // old bearing, tilt and roll code + QDoubleMatrix4x4 mBearing; + mBearing.rotate(m_cameraData.bearing(), m_view); + m_up = mBearing * m_up; + } + + m_side = QDoubleVector3D::normal(m_up, m_view); + + if (m_cameraData.tilt() > 0.0) { + QDoubleMatrix4x4 mTilt; + mTilt.rotate(-m_cameraData.tilt(), m_side); + m_eye = mTilt * m_view + m_center; + } + + m_view = m_eye - m_center; + m_viewNormalized = m_view.normalized(); + m_up = QDoubleVector3D::normal(m_view, m_side); + + m_nearPlane = 1; + // 10000.0 to make sure that everything fits in the frustum even at high zoom levels + // (that is, with a very large map) + // TODO: extend this to support clip distance + m_farPlane = (altitude + 10000.0) * defaultTileSize; + + double aspectRatio = 1.0 * m_viewportWidth / m_viewportHeight; + + m_halfWidth = 1 * m_aperture; + m_halfHeight = 1 * m_aperture; + if (aspectRatio > 1.0) { + m_halfWidth *= aspectRatio; + } else if (aspectRatio > 0.0 && aspectRatio < 1.0) { + m_halfHeight /= aspectRatio; + } + + QDoubleMatrix4x4 cameraMatrix; + cameraMatrix.lookAt(m_eye, m_center, m_up); + + QDoubleMatrix4x4 projectionMatrix; + projectionMatrix.frustum(-m_halfWidth, m_halfWidth, -m_halfHeight, m_halfHeight, m_nearPlane, m_farPlane); + + m_transformation = projectionMatrix * cameraMatrix; + m_centerNearPlane = m_eye + m_view.normalized(); } QT_END_NAMESPACE diff --git a/src/location/maps/qgeoprojection_p.h b/src/location/maps/qgeoprojection_p.h index f36bf9ac..61059fdd 100644 --- a/src/location/maps/qgeoprojection_p.h +++ b/src/location/maps/qgeoprojection_p.h @@ -50,7 +50,7 @@ #include #include -#include +#include QT_BEGIN_NAMESPACE @@ -116,6 +116,7 @@ public: QGeoCoordinate itemPositionToCoordinate(const QDoubleVector2D &pos, bool clipToViewport = true) const Q_DECL_OVERRIDE; QDoubleVector2D coordinateToItemPosition(const QGeoCoordinate &coordinate, bool clipToViewport = true) const Q_DECL_OVERRIDE; + bool isProjectable(const QDoubleVector2D &wrappedProjection) const; private: void setupCamera(); @@ -126,16 +127,45 @@ private: // mercator to camera transform for coordinates (not tiles!) double m_cameraCenterXMercator; double m_cameraCenterYMercator; - double m_cameraWidthMercator; - double m_cameraHeightMercator; - double m_1_cameraWidthMercator; - double m_1_cameraHeightMercator; // cameraToScreen transform double m_viewportWidth; // in pixels double m_viewportHeight; // in pixels double m_1_viewportWidth; double m_1_viewportHeight; + + QDoubleMatrix4x4 m_transformation; + QDoubleVector3D m_eye; + QDoubleVector3D m_up; + QDoubleVector3D m_center; + QDoubleVector3D m_view; + QDoubleVector3D m_viewNormalized; + QDoubleVector3D m_side; + QDoubleVector3D m_centerNearPlane; + double m_sideLength; // map edge size at integer zoom level + double m_aperture; + double m_nearPlane; + double m_farPlane; + double m_halfWidth; + double m_halfHeight; + + struct Plane + { + Plane() {} + Plane(const QDoubleVector3D &planePoint, const QDoubleVector3D &planeNormal) + : m_point(planePoint), m_normal(planeNormal) { } + + QDoubleVector3D lineIntersection(const QDoubleVector3D &linePoint, const QDoubleVector3D &lineDirection) const + { + QDoubleVector3D w = linePoint - m_point; + // s = -n.dot(w) / n.dot(u). p = p0 + su; u is lineDirection + double s = QDoubleVector3D::dotProduct(-m_normal, w) / QDoubleVector3D::dotProduct(m_normal, lineDirection); + return linePoint + lineDirection * s; + } + + QDoubleVector3D m_point; + QDoubleVector3D m_normal; + } m_plane; }; QT_END_NAMESPACE diff --git a/src/positioning/positioning.pro b/src/positioning/positioning.pro index b5c6ee46..c4c9cc6c 100644 --- a/src/positioning/positioning.pro +++ b/src/positioning/positioning.pro @@ -52,6 +52,7 @@ PRIVATE_HEADERS += \ qwebmercator_p.h \ qpositioningglobal_p.h \ qlocationdata_simulator_p.h \ + qdoublematrix4x4_p.h \ qgeopath_p.h SOURCES += \ @@ -76,7 +77,8 @@ SOURCES += \ qdoublevector3d.cpp \ qgeopath.cpp \ qlocationdata_simulator.cpp \ - qwebmercator.cpp + qwebmercator.cpp \ + qdoublematrix4x4.cpp HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS diff --git a/src/positioning/qdoublematrix4x4.cpp b/src/positioning/qdoublematrix4x4.cpp new file mode 100644 index 00000000..bca38075 --- /dev/null +++ b/src/positioning/qdoublematrix4x4.cpp @@ -0,0 +1,1111 @@ +/**************************************************************************** +** +** 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 + +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 = angle * M_PI / 180.0; + 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 = angle * M_PI / 180.0; + 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 = (verticalAngle / 2.0) * M_PI / 180.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 diff --git a/src/positioning/qdoublematrix4x4_p.h b/src/positioning/qdoublematrix4x4_p.h new file mode 100644 index 00000000..3ce7b312 --- /dev/null +++ b/src/positioning/qdoublematrix4x4_p.h @@ -0,0 +1,946 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QDOUBLEMATRIX4X4_H +#define QDOUBLEMATRIX4X4_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/* + * This class is a copy/paste/replace of QMatrix4x4 + * No algorithm has been changed. + * Some methods have been removed. + */ + +class Q_POSITIONING_PRIVATE_EXPORT QDoubleMatrix4x4 +{ +public: + inline QDoubleMatrix4x4() { setToIdentity(); } + explicit QDoubleMatrix4x4(Qt::Initialization) : flagBits(General) {} + explicit QDoubleMatrix4x4(const double *values); + inline QDoubleMatrix4x4(double m11, double m12, double m13, double m14, + double m21, double m22, double m23, double m24, + double m31, double m32, double m33, double m34, + double m41, double m42, double m43, double m44); + + QDoubleMatrix4x4(const double *values, int cols, int rows); + + inline const double& operator()(int row, int column) const; + inline double& operator()(int row, int column); + + inline bool isAffine() const; + + inline bool isIdentity() const; + inline void setToIdentity(); + + inline void fill(double value); + + double determinant() const; + QDoubleMatrix4x4 inverted(bool *invertible = Q_NULLPTR) const; + QDoubleMatrix4x4 transposed() const; + + inline QDoubleMatrix4x4& operator+=(const QDoubleMatrix4x4& other); + inline QDoubleMatrix4x4& operator-=(const QDoubleMatrix4x4& other); + inline QDoubleMatrix4x4& operator*=(const QDoubleMatrix4x4& other); + inline QDoubleMatrix4x4& operator*=(double factor); + QDoubleMatrix4x4& operator/=(double divisor); + inline bool operator==(const QDoubleMatrix4x4& other) const; + inline bool operator!=(const QDoubleMatrix4x4& other) const; + + friend QDoubleMatrix4x4 operator+(const QDoubleMatrix4x4& m1, const QDoubleMatrix4x4& m2); + friend QDoubleMatrix4x4 operator-(const QDoubleMatrix4x4& m1, const QDoubleMatrix4x4& m2); + friend QDoubleMatrix4x4 operator*(const QDoubleMatrix4x4& m1, const QDoubleMatrix4x4& m2); + + friend QDoubleVector3D operator*(const QDoubleMatrix4x4& matrix, const QDoubleVector3D& vector); + friend QDoubleVector3D operator*(const QDoubleVector3D& vector, const QDoubleMatrix4x4& matrix); + + friend QPoint operator*(const QPoint& point, const QDoubleMatrix4x4& matrix); + friend QPointF operator*(const QPointF& point, const QDoubleMatrix4x4& matrix); + friend QDoubleMatrix4x4 operator-(const QDoubleMatrix4x4& matrix); + friend QPoint operator*(const QDoubleMatrix4x4& matrix, const QPoint& point); + friend QPointF operator*(const QDoubleMatrix4x4& matrix, const QPointF& point); + friend QDoubleMatrix4x4 operator*(double factor, const QDoubleMatrix4x4& matrix); + friend QDoubleMatrix4x4 operator*(const QDoubleMatrix4x4& matrix, double factor); + friend Q_POSITIONING_PRIVATE_EXPORT QDoubleMatrix4x4 operator/(const QDoubleMatrix4x4& matrix, double divisor); + + friend inline bool qFuzzyCompare(const QDoubleMatrix4x4& m1, const QDoubleMatrix4x4& m2); + + + void scale(const QDoubleVector3D& vector); + void translate(const QDoubleVector3D& vector); + void rotate(double angle, const QDoubleVector3D& vector); + + void scale(double x, double y); + void scale(double x, double y, double z); + void scale(double factor); + void translate(double x, double y); + void translate(double x, double y, double z); + void rotate(double angle, double x, double y, double z = 0.0f); + + void ortho(const QRect& rect); + void ortho(const QRectF& rect); + void ortho(double left, double right, double bottom, double top, double nearPlane, double farPlane); + void frustum(double left, double right, double bottom, double top, double nearPlane, double farPlane); + void perspective(double verticalAngle, double aspectRatio, double nearPlane, double farPlane); + + void lookAt(const QDoubleVector3D& eye, const QDoubleVector3D& center, const QDoubleVector3D& up); + + void viewport(const QRectF &rect); + void viewport(double left, double bottom, double width, double height, double nearPlane = 0.0f, double farPlane = 1.0f); + void flipCoordinates(); + + void copyDataTo(double *values) const; + + QPoint map(const QPoint& point) const; + QPointF map(const QPointF& point) const; + + QDoubleVector3D map(const QDoubleVector3D& point) const; + QDoubleVector3D mapVector(const QDoubleVector3D& vector) const; + + QRect mapRect(const QRect& rect) const; + QRectF mapRect(const QRectF& rect) const; + + inline double *data(); + inline const double *data() const { return *m; } + inline const double *constData() const { return *m; } + + void optimize(); + +#ifndef QT_NO_DEBUG_STREAM + friend Q_POSITIONING_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QDoubleMatrix4x4 &m); +#endif + +private: + double m[4][4]; // Column-major order to match OpenGL. + int flagBits; // Flag bits from the enum below. + + // When matrices are multiplied, the flag bits are or-ed together. + enum { + Identity = 0x0000, // Identity matrix + Translation = 0x0001, // Contains a translation + Scale = 0x0002, // Contains a scale + Rotation2D = 0x0004, // Contains a rotation about the Z axis + Rotation = 0x0008, // Contains an arbitrary rotation + Perspective = 0x0010, // Last row is different from (0, 0, 0, 1) + General = 0x001f // General matrix, unknown contents + }; + + // Construct without initializing identity matrix. + explicit QDoubleMatrix4x4(int) { } + + QDoubleMatrix4x4 orthonormalInverse() const; + + void projectedRotate(double angle, double x, double y, double z); +}; + +Q_DECLARE_TYPEINFO(QDoubleMatrix4x4, Q_MOVABLE_TYPE); + +inline QDoubleMatrix4x4::QDoubleMatrix4x4 + (double m11, double m12, double m13, double m14, + double m21, double m22, double m23, double m24, + double m31, double m32, double m33, double m34, + double m41, double m42, double m43, double m44) +{ + m[0][0] = m11; m[0][1] = m21; m[0][2] = m31; m[0][3] = m41; + m[1][0] = m12; m[1][1] = m22; m[1][2] = m32; m[1][3] = m42; + m[2][0] = m13; m[2][1] = m23; m[2][2] = m33; m[2][3] = m43; + m[3][0] = m14; m[3][1] = m24; m[3][2] = m34; m[3][3] = m44; + flagBits = General; +} + +inline const double& QDoubleMatrix4x4::operator()(int aRow, int aColumn) const +{ + Q_ASSERT(aRow >= 0 && aRow < 4 && aColumn >= 0 && aColumn < 4); + return m[aColumn][aRow]; +} + +inline double& QDoubleMatrix4x4::operator()(int aRow, int aColumn) +{ + Q_ASSERT(aRow >= 0 && aRow < 4 && aColumn >= 0 && aColumn < 4); + flagBits = General; + return m[aColumn][aRow]; +} + +Q_POSITIONING_PRIVATE_EXPORT QDoubleMatrix4x4 operator/(const QDoubleMatrix4x4& matrix, double divisor); + +inline bool QDoubleMatrix4x4::isAffine() const +{ + return m[0][3] == 0.0f && m[1][3] == 0.0f && m[2][3] == 0.0f && m[3][3] == 1.0f; +} + +inline bool QDoubleMatrix4x4::isIdentity() const +{ + if (flagBits == Identity) + return true; + if (m[0][0] != 1.0f || m[0][1] != 0.0f || m[0][2] != 0.0f) + return false; + if (m[0][3] != 0.0f || m[1][0] != 0.0f || m[1][1] != 1.0f) + return false; + if (m[1][2] != 0.0f || m[1][3] != 0.0f || m[2][0] != 0.0f) + return false; + if (m[2][1] != 0.0f || m[2][2] != 1.0f || m[2][3] != 0.0f) + return false; + if (m[3][0] != 0.0f || m[3][1] != 0.0f || m[3][2] != 0.0f) + return false; + return (m[3][3] == 1.0f); +} + +inline void QDoubleMatrix4x4::setToIdentity() +{ + m[0][0] = 1.0f; + m[0][1] = 0.0f; + m[0][2] = 0.0f; + m[0][3] = 0.0f; + m[1][0] = 0.0f; + m[1][1] = 1.0f; + m[1][2] = 0.0f; + m[1][3] = 0.0f; + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = 1.0f; + m[2][3] = 0.0f; + m[3][0] = 0.0f; + m[3][1] = 0.0f; + m[3][2] = 0.0f; + m[3][3] = 1.0f; + flagBits = Identity; +} + +inline void QDoubleMatrix4x4::fill(double value) +{ + m[0][0] = value; + m[0][1] = value; + m[0][2] = value; + m[0][3] = value; + m[1][0] = value; + m[1][1] = value; + m[1][2] = value; + m[1][3] = value; + m[2][0] = value; + m[2][1] = value; + m[2][2] = value; + m[2][3] = value; + m[3][0] = value; + m[3][1] = value; + m[3][2] = value; + m[3][3] = value; + flagBits = General; +} + +inline QDoubleMatrix4x4& QDoubleMatrix4x4::operator+=(const QDoubleMatrix4x4& other) +{ + m[0][0] += other.m[0][0]; + m[0][1] += other.m[0][1]; + m[0][2] += other.m[0][2]; + m[0][3] += other.m[0][3]; + m[1][0] += other.m[1][0]; + m[1][1] += other.m[1][1]; + m[1][2] += other.m[1][2]; + m[1][3] += other.m[1][3]; + m[2][0] += other.m[2][0]; + m[2][1] += other.m[2][1]; + m[2][2] += other.m[2][2]; + m[2][3] += other.m[2][3]; + m[3][0] += other.m[3][0]; + m[3][1] += other.m[3][1]; + m[3][2] += other.m[3][2]; + m[3][3] += other.m[3][3]; + flagBits = General; + return *this; +} + +inline QDoubleMatrix4x4& QDoubleMatrix4x4::operator-=(const QDoubleMatrix4x4& other) +{ + m[0][0] -= other.m[0][0]; + m[0][1] -= other.m[0][1]; + m[0][2] -= other.m[0][2]; + m[0][3] -= other.m[0][3]; + m[1][0] -= other.m[1][0]; + m[1][1] -= other.m[1][1]; + m[1][2] -= other.m[1][2]; + m[1][3] -= other.m[1][3]; + m[2][0] -= other.m[2][0]; + m[2][1] -= other.m[2][1]; + m[2][2] -= other.m[2][2]; + m[2][3] -= other.m[2][3]; + m[3][0] -= other.m[3][0]; + m[3][1] -= other.m[3][1]; + m[3][2] -= other.m[3][2]; + m[3][3] -= other.m[3][3]; + flagBits = General; + return *this; +} + +inline QDoubleMatrix4x4& QDoubleMatrix4x4::operator*=(const QDoubleMatrix4x4& other) +{ + flagBits |= other.flagBits; + + if (flagBits < Rotation2D) { + m[3][0] += m[0][0] * other.m[3][0]; + m[3][1] += m[1][1] * other.m[3][1]; + m[3][2] += m[2][2] * other.m[3][2]; + + m[0][0] *= other.m[0][0]; + m[1][1] *= other.m[1][1]; + m[2][2] *= other.m[2][2]; + return *this; + } + + double m0, m1, m2; + m0 = m[0][0] * other.m[0][0] + + m[1][0] * other.m[0][1] + + m[2][0] * other.m[0][2] + + m[3][0] * other.m[0][3]; + m1 = m[0][0] * other.m[1][0] + + m[1][0] * other.m[1][1] + + m[2][0] * other.m[1][2] + + m[3][0] * other.m[1][3]; + m2 = m[0][0] * other.m[2][0] + + m[1][0] * other.m[2][1] + + m[2][0] * other.m[2][2] + + m[3][0] * other.m[2][3]; + m[3][0] = m[0][0] * other.m[3][0] + + m[1][0] * other.m[3][1] + + m[2][0] * other.m[3][2] + + m[3][0] * other.m[3][3]; + m[0][0] = m0; + m[1][0] = m1; + m[2][0] = m2; + + m0 = m[0][1] * other.m[0][0] + + m[1][1] * other.m[0][1] + + m[2][1] * other.m[0][2] + + m[3][1] * other.m[0][3]; + m1 = m[0][1] * other.m[1][0] + + m[1][1] * other.m[1][1] + + m[2][1] * other.m[1][2] + + m[3][1] * other.m[1][3]; + m2 = m[0][1] * other.m[2][0] + + m[1][1] * other.m[2][1] + + m[2][1] * other.m[2][2] + + m[3][1] * other.m[2][3]; + m[3][1] = m[0][1] * other.m[3][0] + + m[1][1] * other.m[3][1] + + m[2][1] * other.m[3][2] + + m[3][1] * other.m[3][3]; + m[0][1] = m0; + m[1][1] = m1; + m[2][1] = m2; + + m0 = m[0][2] * other.m[0][0] + + m[1][2] * other.m[0][1] + + m[2][2] * other.m[0][2] + + m[3][2] * other.m[0][3]; + m1 = m[0][2] * other.m[1][0] + + m[1][2] * other.m[1][1] + + m[2][2] * other.m[1][2] + + m[3][2] * other.m[1][3]; + m2 = m[0][2] * other.m[2][0] + + m[1][2] * other.m[2][1] + + m[2][2] * other.m[2][2] + + m[3][2] * other.m[2][3]; + m[3][2] = m[0][2] * other.m[3][0] + + m[1][2] * other.m[3][1] + + m[2][2] * other.m[3][2] + + m[3][2] * other.m[3][3]; + m[0][2] = m0; + m[1][2] = m1; + m[2][2] = m2; + + m0 = m[0][3] * other.m[0][0] + + m[1][3] * other.m[0][1] + + m[2][3] * other.m[0][2] + + m[3][3] * other.m[0][3]; + m1 = m[0][3] * other.m[1][0] + + m[1][3] * other.m[1][1] + + m[2][3] * other.m[1][2] + + m[3][3] * other.m[1][3]; + m2 = m[0][3] * other.m[2][0] + + m[1][3] * other.m[2][1] + + m[2][3] * other.m[2][2] + + m[3][3] * other.m[2][3]; + m[3][3] = m[0][3] * other.m[3][0] + + m[1][3] * other.m[3][1] + + m[2][3] * other.m[3][2] + + m[3][3] * other.m[3][3]; + m[0][3] = m0; + m[1][3] = m1; + m[2][3] = m2; + return *this; +} + +inline QDoubleMatrix4x4& QDoubleMatrix4x4::operator*=(double factor) +{ + 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; + m[3][0] *= factor; + m[3][1] *= factor; + m[3][2] *= factor; + m[3][3] *= factor; + flagBits = General; + return *this; +} + +inline bool QDoubleMatrix4x4::operator==(const QDoubleMatrix4x4& other) const +{ + return m[0][0] == other.m[0][0] && + m[0][1] == other.m[0][1] && + m[0][2] == other.m[0][2] && + m[0][3] == other.m[0][3] && + m[1][0] == other.m[1][0] && + m[1][1] == other.m[1][1] && + m[1][2] == other.m[1][2] && + m[1][3] == other.m[1][3] && + m[2][0] == other.m[2][0] && + m[2][1] == other.m[2][1] && + m[2][2] == other.m[2][2] && + m[2][3] == other.m[2][3] && + m[3][0] == other.m[3][0] && + m[3][1] == other.m[3][1] && + m[3][2] == other.m[3][2] && + m[3][3] == other.m[3][3]; +} + +inline bool QDoubleMatrix4x4::operator!=(const QDoubleMatrix4x4& other) const +{ + return m[0][0] != other.m[0][0] || + m[0][1] != other.m[0][1] || + m[0][2] != other.m[0][2] || + m[0][3] != other.m[0][3] || + m[1][0] != other.m[1][0] || + m[1][1] != other.m[1][1] || + m[1][2] != other.m[1][2] || + m[1][3] != other.m[1][3] || + m[2][0] != other.m[2][0] || + m[2][1] != other.m[2][1] || + m[2][2] != other.m[2][2] || + m[2][3] != other.m[2][3] || + m[3][0] != other.m[3][0] || + m[3][1] != other.m[3][1] || + m[3][2] != other.m[3][2] || + m[3][3] != other.m[3][3]; +} + +inline QDoubleMatrix4x4 operator+(const QDoubleMatrix4x4& m1, const QDoubleMatrix4x4& m2) +{ + QDoubleMatrix4x4 m(1); + m.m[0][0] = m1.m[0][0] + m2.m[0][0]; + m.m[0][1] = m1.m[0][1] + m2.m[0][1]; + m.m[0][2] = m1.m[0][2] + m2.m[0][2]; + m.m[0][3] = m1.m[0][3] + m2.m[0][3]; + m.m[1][0] = m1.m[1][0] + m2.m[1][0]; + m.m[1][1] = m1.m[1][1] + m2.m[1][1]; + m.m[1][2] = m1.m[1][2] + m2.m[1][2]; + m.m[1][3] = m1.m[1][3] + m2.m[1][3]; + m.m[2][0] = m1.m[2][0] + m2.m[2][0]; + m.m[2][1] = m1.m[2][1] + m2.m[2][1]; + m.m[2][2] = m1.m[2][2] + m2.m[2][2]; + m.m[2][3] = m1.m[2][3] + m2.m[2][3]; + m.m[3][0] = m1.m[3][0] + m2.m[3][0]; + m.m[3][1] = m1.m[3][1] + m2.m[3][1]; + m.m[3][2] = m1.m[3][2] + m2.m[3][2]; + m.m[3][3] = m1.m[3][3] + m2.m[3][3]; + m.flagBits = QDoubleMatrix4x4::General; + return m; +} + +inline QDoubleMatrix4x4 operator-(const QDoubleMatrix4x4& m1, const QDoubleMatrix4x4& m2) +{ + QDoubleMatrix4x4 m(1); + m.m[0][0] = m1.m[0][0] - m2.m[0][0]; + m.m[0][1] = m1.m[0][1] - m2.m[0][1]; + m.m[0][2] = m1.m[0][2] - m2.m[0][2]; + m.m[0][3] = m1.m[0][3] - m2.m[0][3]; + m.m[1][0] = m1.m[1][0] - m2.m[1][0]; + m.m[1][1] = m1.m[1][1] - m2.m[1][1]; + m.m[1][2] = m1.m[1][2] - m2.m[1][2]; + m.m[1][3] = m1.m[1][3] - m2.m[1][3]; + m.m[2][0] = m1.m[2][0] - m2.m[2][0]; + m.m[2][1] = m1.m[2][1] - m2.m[2][1]; + m.m[2][2] = m1.m[2][2] - m2.m[2][2]; + m.m[2][3] = m1.m[2][3] - m2.m[2][3]; + m.m[3][0] = m1.m[3][0] - m2.m[3][0]; + m.m[3][1] = m1.m[3][1] - m2.m[3][1]; + m.m[3][2] = m1.m[3][2] - m2.m[3][2]; + m.m[3][3] = m1.m[3][3] - m2.m[3][3]; + m.flagBits = QDoubleMatrix4x4::General; + return m; +} + +inline QDoubleMatrix4x4 operator*(const QDoubleMatrix4x4& m1, const QDoubleMatrix4x4& m2) +{ + int flagBits = m1.flagBits | m2.flagBits; + if (flagBits < QDoubleMatrix4x4::Rotation2D) { + QDoubleMatrix4x4 m = m1; + m.m[3][0] += m.m[0][0] * m2.m[3][0]; + m.m[3][1] += m.m[1][1] * m2.m[3][1]; + m.m[3][2] += m.m[2][2] * m2.m[3][2]; + + m.m[0][0] *= m2.m[0][0]; + m.m[1][1] *= m2.m[1][1]; + m.m[2][2] *= m2.m[2][2]; + m.flagBits = flagBits; + return m; + } + + QDoubleMatrix4x4 m(1); + m.m[0][0] = m1.m[0][0] * m2.m[0][0] + + m1.m[1][0] * m2.m[0][1] + + m1.m[2][0] * m2.m[0][2] + + m1.m[3][0] * m2.m[0][3]; + m.m[0][1] = m1.m[0][1] * m2.m[0][0] + + m1.m[1][1] * m2.m[0][1] + + m1.m[2][1] * m2.m[0][2] + + m1.m[3][1] * m2.m[0][3]; + m.m[0][2] = m1.m[0][2] * m2.m[0][0] + + m1.m[1][2] * m2.m[0][1] + + m1.m[2][2] * m2.m[0][2] + + m1.m[3][2] * m2.m[0][3]; + m.m[0][3] = m1.m[0][3] * m2.m[0][0] + + m1.m[1][3] * m2.m[0][1] + + m1.m[2][3] * m2.m[0][2] + + m1.m[3][3] * m2.m[0][3]; + + m.m[1][0] = m1.m[0][0] * m2.m[1][0] + + m1.m[1][0] * m2.m[1][1] + + m1.m[2][0] * m2.m[1][2] + + m1.m[3][0] * m2.m[1][3]; + m.m[1][1] = m1.m[0][1] * m2.m[1][0] + + m1.m[1][1] * m2.m[1][1] + + m1.m[2][1] * m2.m[1][2] + + m1.m[3][1] * m2.m[1][3]; + m.m[1][2] = m1.m[0][2] * m2.m[1][0] + + m1.m[1][2] * m2.m[1][1] + + m1.m[2][2] * m2.m[1][2] + + m1.m[3][2] * m2.m[1][3]; + m.m[1][3] = m1.m[0][3] * m2.m[1][0] + + m1.m[1][3] * m2.m[1][1] + + m1.m[2][3] * m2.m[1][2] + + m1.m[3][3] * m2.m[1][3]; + + m.m[2][0] = m1.m[0][0] * m2.m[2][0] + + m1.m[1][0] * m2.m[2][1] + + m1.m[2][0] * m2.m[2][2] + + m1.m[3][0] * m2.m[2][3]; + m.m[2][1] = m1.m[0][1] * m2.m[2][0] + + m1.m[1][1] * m2.m[2][1] + + m1.m[2][1] * m2.m[2][2] + + m1.m[3][1] * m2.m[2][3]; + m.m[2][2] = m1.m[0][2] * m2.m[2][0] + + m1.m[1][2] * m2.m[2][1] + + m1.m[2][2] * m2.m[2][2] + + m1.m[3][2] * m2.m[2][3]; + m.m[2][3] = m1.m[0][3] * m2.m[2][0] + + m1.m[1][3] * m2.m[2][1] + + m1.m[2][3] * m2.m[2][2] + + m1.m[3][3] * m2.m[2][3]; + + m.m[3][0] = m1.m[0][0] * m2.m[3][0] + + m1.m[1][0] * m2.m[3][1] + + m1.m[2][0] * m2.m[3][2] + + m1.m[3][0] * m2.m[3][3]; + m.m[3][1] = m1.m[0][1] * m2.m[3][0] + + m1.m[1][1] * m2.m[3][1] + + m1.m[2][1] * m2.m[3][2] + + m1.m[3][1] * m2.m[3][3]; + m.m[3][2] = m1.m[0][2] * m2.m[3][0] + + m1.m[1][2] * m2.m[3][1] + + m1.m[2][2] * m2.m[3][2] + + m1.m[3][2] * m2.m[3][3]; + m.m[3][3] = m1.m[0][3] * m2.m[3][0] + + m1.m[1][3] * m2.m[3][1] + + m1.m[2][3] * m2.m[3][2] + + m1.m[3][3] * m2.m[3][3]; + m.flagBits = flagBits; + return m; +} + +inline QDoubleVector3D operator*(const QDoubleVector3D& vector, const QDoubleMatrix4x4& matrix) +{ + double x, y, z, w; + x = vector.x() * matrix.m[0][0] + + vector.y() * matrix.m[0][1] + + vector.z() * matrix.m[0][2] + + matrix.m[0][3]; + y = vector.x() * matrix.m[1][0] + + vector.y() * matrix.m[1][1] + + vector.z() * matrix.m[1][2] + + matrix.m[1][3]; + z = vector.x() * matrix.m[2][0] + + vector.y() * matrix.m[2][1] + + vector.z() * matrix.m[2][2] + + matrix.m[2][3]; + w = vector.x() * matrix.m[3][0] + + vector.y() * matrix.m[3][1] + + vector.z() * matrix.m[3][2] + + matrix.m[3][3]; + if (w == 1.0f) + return QDoubleVector3D(x, y, z); + else + return QDoubleVector3D(x / w, y / w, z / w); +} + +inline QDoubleVector3D operator*(const QDoubleMatrix4x4& matrix, const QDoubleVector3D& vector) +{ + double x, y, z, w; + if (matrix.flagBits == QDoubleMatrix4x4::Identity) { + return vector; + } else if (matrix.flagBits < QDoubleMatrix4x4::Rotation2D) { + // Translation | Scale + return QDoubleVector3D(vector.x() * matrix.m[0][0] + matrix.m[3][0], + vector.y() * matrix.m[1][1] + matrix.m[3][1], + vector.z() * matrix.m[2][2] + matrix.m[3][2]); + } else if (matrix.flagBits < QDoubleMatrix4x4::Rotation) { + // Translation | Scale | Rotation2D + return QDoubleVector3D(vector.x() * matrix.m[0][0] + vector.y() * matrix.m[1][0] + matrix.m[3][0], + vector.x() * matrix.m[0][1] + vector.y() * matrix.m[1][1] + matrix.m[3][1], + vector.z() * matrix.m[2][2] + matrix.m[3][2]); + } else { + x = vector.x() * matrix.m[0][0] + + vector.y() * matrix.m[1][0] + + vector.z() * matrix.m[2][0] + + matrix.m[3][0]; + y = vector.x() * matrix.m[0][1] + + vector.y() * matrix.m[1][1] + + vector.z() * matrix.m[2][1] + + matrix.m[3][1]; + z = vector.x() * matrix.m[0][2] + + vector.y() * matrix.m[1][2] + + vector.z() * matrix.m[2][2] + + matrix.m[3][2]; + w = vector.x() * matrix.m[0][3] + + vector.y() * matrix.m[1][3] + + vector.z() * matrix.m[2][3] + + matrix.m[3][3]; + if (w == 1.0f) + return QDoubleVector3D(x, y, z); + else + return QDoubleVector3D(x / w, y / w, z / w); + } +} + +inline QPoint operator*(const QPoint& point, const QDoubleMatrix4x4& matrix) +{ + double xin, yin; + double x, y, w; + xin = point.x(); + yin = point.y(); + x = xin * matrix.m[0][0] + + yin * matrix.m[0][1] + + matrix.m[0][3]; + y = xin * matrix.m[1][0] + + yin * matrix.m[1][1] + + matrix.m[1][3]; + w = xin * matrix.m[3][0] + + yin * matrix.m[3][1] + + matrix.m[3][3]; + if (w == 1.0f) + return QPoint(qRound(x), qRound(y)); + else + return QPoint(qRound(x / w), qRound(y / w)); +} + +inline QPointF operator*(const QPointF& point, const QDoubleMatrix4x4& matrix) +{ + double xin, yin; + double x, y, w; + xin = point.x(); + yin = point.y(); + x = xin * matrix.m[0][0] + + yin * matrix.m[0][1] + + matrix.m[0][3]; + y = xin * matrix.m[1][0] + + yin * matrix.m[1][1] + + matrix.m[1][3]; + w = xin * matrix.m[3][0] + + yin * matrix.m[3][1] + + matrix.m[3][3]; + if (w == 1.0f) { + return QPointF(double(x), double(y)); + } else { + return QPointF(double(x / w), double(y / w)); + } +} + +inline QPoint operator*(const QDoubleMatrix4x4& matrix, const QPoint& point) +{ + double xin, yin; + double x, y, w; + xin = point.x(); + yin = point.y(); + if (matrix.flagBits == QDoubleMatrix4x4::Identity) { + return point; + } else if (matrix.flagBits < QDoubleMatrix4x4::Rotation2D) { + // Translation | Scale + return QPoint(qRound(xin * matrix.m[0][0] + matrix.m[3][0]), + qRound(yin * matrix.m[1][1] + matrix.m[3][1])); + } else if (matrix.flagBits < QDoubleMatrix4x4::Perspective) { + return QPoint(qRound(xin * matrix.m[0][0] + yin * matrix.m[1][0] + matrix.m[3][0]), + qRound(xin * matrix.m[0][1] + yin * matrix.m[1][1] + matrix.m[3][1])); + } else { + x = xin * matrix.m[0][0] + + yin * matrix.m[1][0] + + matrix.m[3][0]; + y = xin * matrix.m[0][1] + + yin * matrix.m[1][1] + + matrix.m[3][1]; + w = xin * matrix.m[0][3] + + yin * matrix.m[1][3] + + matrix.m[3][3]; + if (w == 1.0f) + return QPoint(qRound(x), qRound(y)); + else + return QPoint(qRound(x / w), qRound(y / w)); + } +} + +inline QPointF operator*(const QDoubleMatrix4x4& matrix, const QPointF& point) +{ + double xin, yin; + double x, y, w; + xin = point.x(); + yin = point.y(); + if (matrix.flagBits == QDoubleMatrix4x4::Identity) { + return point; + } else if (matrix.flagBits < QDoubleMatrix4x4::Rotation2D) { + // Translation | Scale + return QPointF(xin * matrix.m[0][0] + matrix.m[3][0], + yin * matrix.m[1][1] + matrix.m[3][1]); + } else if (matrix.flagBits < QDoubleMatrix4x4::Perspective) { + return QPointF(xin * matrix.m[0][0] + yin * matrix.m[1][0] + matrix.m[3][0], + xin * matrix.m[0][1] + yin * matrix.m[1][1] + matrix.m[3][1]); + } else { + x = xin * matrix.m[0][0] + + yin * matrix.m[1][0] + + matrix.m[3][0]; + y = xin * matrix.m[0][1] + + yin * matrix.m[1][1] + + matrix.m[3][1]; + w = xin * matrix.m[0][3] + + yin * matrix.m[1][3] + + matrix.m[3][3]; + if (w == 1.0f) { + return QPointF(double(x), double(y)); + } else { + return QPointF(double(x / w), double(y / w)); + } + } +} + +inline QDoubleMatrix4x4 operator-(const QDoubleMatrix4x4& matrix) +{ + QDoubleMatrix4x4 m(1); + m.m[0][0] = -matrix.m[0][0]; + m.m[0][1] = -matrix.m[0][1]; + m.m[0][2] = -matrix.m[0][2]; + m.m[0][3] = -matrix.m[0][3]; + m.m[1][0] = -matrix.m[1][0]; + m.m[1][1] = -matrix.m[1][1]; + m.m[1][2] = -matrix.m[1][2]; + m.m[1][3] = -matrix.m[1][3]; + m.m[2][0] = -matrix.m[2][0]; + m.m[2][1] = -matrix.m[2][1]; + m.m[2][2] = -matrix.m[2][2]; + m.m[2][3] = -matrix.m[2][3]; + m.m[3][0] = -matrix.m[3][0]; + m.m[3][1] = -matrix.m[3][1]; + m.m[3][2] = -matrix.m[3][2]; + m.m[3][3] = -matrix.m[3][3]; + m.flagBits = QDoubleMatrix4x4::General; + return m; +} + +inline QDoubleMatrix4x4 operator*(double factor, const QDoubleMatrix4x4& matrix) +{ + QDoubleMatrix4x4 m(1); + m.m[0][0] = matrix.m[0][0] * factor; + m.m[0][1] = matrix.m[0][1] * factor; + m.m[0][2] = matrix.m[0][2] * factor; + m.m[0][3] = matrix.m[0][3] * factor; + m.m[1][0] = matrix.m[1][0] * factor; + m.m[1][1] = matrix.m[1][1] * factor; + m.m[1][2] = matrix.m[1][2] * factor; + m.m[1][3] = matrix.m[1][3] * factor; + m.m[2][0] = matrix.m[2][0] * factor; + m.m[2][1] = matrix.m[2][1] * factor; + m.m[2][2] = matrix.m[2][2] * factor; + m.m[2][3] = matrix.m[2][3] * factor; + m.m[3][0] = matrix.m[3][0] * factor; + m.m[3][1] = matrix.m[3][1] * factor; + m.m[3][2] = matrix.m[3][2] * factor; + m.m[3][3] = matrix.m[3][3] * factor; + m.flagBits = QDoubleMatrix4x4::General; + return m; +} + +inline QDoubleMatrix4x4 operator*(const QDoubleMatrix4x4& matrix, double factor) +{ + QDoubleMatrix4x4 m(1); + m.m[0][0] = matrix.m[0][0] * factor; + m.m[0][1] = matrix.m[0][1] * factor; + m.m[0][2] = matrix.m[0][2] * factor; + m.m[0][3] = matrix.m[0][3] * factor; + m.m[1][0] = matrix.m[1][0] * factor; + m.m[1][1] = matrix.m[1][1] * factor; + m.m[1][2] = matrix.m[1][2] * factor; + m.m[1][3] = matrix.m[1][3] * factor; + m.m[2][0] = matrix.m[2][0] * factor; + m.m[2][1] = matrix.m[2][1] * factor; + m.m[2][2] = matrix.m[2][2] * factor; + m.m[2][3] = matrix.m[2][3] * factor; + m.m[3][0] = matrix.m[3][0] * factor; + m.m[3][1] = matrix.m[3][1] * factor; + m.m[3][2] = matrix.m[3][2] * factor; + m.m[3][3] = matrix.m[3][3] * factor; + m.flagBits = QDoubleMatrix4x4::General; + return m; +} + +inline bool qFuzzyCompare(const QDoubleMatrix4x4& m1, const QDoubleMatrix4x4& m2) +{ + return qFuzzyCompare(m1.m[0][0], m2.m[0][0]) && + qFuzzyCompare(m1.m[0][1], m2.m[0][1]) && + qFuzzyCompare(m1.m[0][2], m2.m[0][2]) && + qFuzzyCompare(m1.m[0][3], m2.m[0][3]) && + qFuzzyCompare(m1.m[1][0], m2.m[1][0]) && + qFuzzyCompare(m1.m[1][1], m2.m[1][1]) && + qFuzzyCompare(m1.m[1][2], m2.m[1][2]) && + qFuzzyCompare(m1.m[1][3], m2.m[1][3]) && + qFuzzyCompare(m1.m[2][0], m2.m[2][0]) && + qFuzzyCompare(m1.m[2][1], m2.m[2][1]) && + qFuzzyCompare(m1.m[2][2], m2.m[2][2]) && + qFuzzyCompare(m1.m[2][3], m2.m[2][3]) && + qFuzzyCompare(m1.m[3][0], m2.m[3][0]) && + qFuzzyCompare(m1.m[3][1], m2.m[3][1]) && + qFuzzyCompare(m1.m[3][2], m2.m[3][2]) && + qFuzzyCompare(m1.m[3][3], m2.m[3][3]); +} + +inline QPoint QDoubleMatrix4x4::map(const QPoint& point) const +{ + return *this * point; +} + +inline QPointF QDoubleMatrix4x4::map(const QPointF& point) const +{ + return *this * point; +} + +inline QDoubleVector3D QDoubleMatrix4x4::map(const QDoubleVector3D& point) const +{ + return *this * point; +} + +inline QDoubleVector3D QDoubleMatrix4x4::mapVector(const QDoubleVector3D& vector) const +{ + if (flagBits < Scale) { + // Translation + return vector; + } else if (flagBits < Rotation2D) { + // Translation | Scale + return QDoubleVector3D(vector.x() * m[0][0], + vector.y() * m[1][1], + vector.z() * m[2][2]); + } else { + return QDoubleVector3D(vector.x() * m[0][0] + + vector.y() * m[1][0] + + vector.z() * m[2][0], + vector.x() * m[0][1] + + vector.y() * m[1][1] + + vector.z() * m[2][1], + vector.x() * m[0][2] + + vector.y() * m[1][2] + + vector.z() * m[2][2]); + } +} + +inline double *QDoubleMatrix4x4::data() +{ + // We have to assume that the caller will modify the matrix elements, + // so we flip it over to "General" mode. + flagBits = General; + return *m; +} + +inline void QDoubleMatrix4x4::viewport(const QRectF &rect) +{ + viewport(rect.x(), rect.y(), rect.width(), rect.height()); +} + +#ifndef QT_NO_DEBUG_STREAM +Q_POSITIONING_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QDoubleMatrix4x4 &m); +#endif + +#ifndef QT_NO_DATASTREAM +Q_POSITIONING_PRIVATE_EXPORT QDataStream &operator<<(QDataStream &, const QDoubleMatrix4x4 &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QDoubleMatrix4x4 &); +#endif + + +QT_END_NAMESPACE + + +#endif // QDOUBLEMATRIX4X4_H diff --git a/src/positioning/qdoublevector2d_p.h b/src/positioning/qdoublevector2d_p.h index c35899e4..69348ee1 100644 --- a/src/positioning/qdoublevector2d_p.h +++ b/src/positioning/qdoublevector2d_p.h @@ -91,6 +91,7 @@ public: inline QDoubleVector2D &operator*=(double factor); inline QDoubleVector2D &operator*=(const QDoubleVector2D &vector); inline QDoubleVector2D &operator/=(double divisor); + inline QDoubleVector2D &operator/=(const QDoubleVector2D &vector); Q_DECL_CONSTEXPR static inline double dotProduct(const QDoubleVector2D &v1, const QDoubleVector2D &v2) { return v1.xp * v2.xp + v1.yp * v2.yp; } @@ -179,6 +180,13 @@ inline QDoubleVector2D &QDoubleVector2D::operator/=(double divisor) return *this; } +inline QDoubleVector2D &QDoubleVector2D::operator/=(const QDoubleVector2D &vector) +{ + xp /= vector.xp; + yp /= vector.yp; + return *this; +} + Q_DECL_CONSTEXPR inline bool operator==(const QDoubleVector2D &v1, const QDoubleVector2D &v2) { return v1.xp == v2.xp && v1.yp == v2.yp; -- cgit v1.2.1