From 595ed595eabe01a1cf11c8b846fd777de8233721 Mon Sep 17 00:00:00 2001 From: Paul Lemire Date: Mon, 19 Jan 2015 12:50:05 +0100 Subject: Add project/unproject methods in QVector3D Equivalent of gluProject and gluUnproject. [ChangeLog][QtCore][QVector3D] add convenience project and unproject methods to use like gluProject and gluUnproject Change-Id: I6e4e3e79ea6e34d1fb0c375e15185c950b699ef0 Reviewed-by: Sean Harmer --- src/gui/math3d/qvector3d.cpp | 65 +++++++++ src/gui/math3d/qvector3d.h | 4 + tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp | 158 ++++++++++++++++++++++ 3 files changed, 227 insertions(+) diff --git a/src/gui/math3d/qvector3d.cpp b/src/gui/math3d/qvector3d.cpp index 75d61e5f85..014987b90d 100644 --- a/src/gui/math3d/qvector3d.cpp +++ b/src/gui/math3d/qvector3d.cpp @@ -34,10 +34,12 @@ #include "qvector3d.h" #include "qvector2d.h" #include "qvector4d.h" +#include "qmatrix4x4.h" #include #include #include #include +#include QT_BEGIN_NAMESPACE @@ -358,6 +360,69 @@ QVector3D QVector3D::normal return crossProduct((v2 - v1), (v3 - v1)).normalized(); } +/*! + \since 5.5 + + Returns the window coordinates of this vector initially in object/model + coordinates using the model view matrix \a modelView, the projection matrix + \a projection and the viewport dimensions \a viewport. + + When transforming from clip to normalized space, a division by the w + component on the vector components takes place. To prevent dividing by 0 if + w equals to 0, it is set to 1. + + \note the returned y coordinates are in OpenGL orientation. OpenGL expects + the bottom to be 0 whereas for Qt top is 0. + + \sa unproject() + */ +QVector3D QVector3D::project(const QMatrix4x4 &modelView, const QMatrix4x4 &projection, const QRect &viewport) const +{ + QVector4D tmp(*this, 1.0f); + tmp = projection * modelView * tmp; + if (qFuzzyIsNull(tmp.w())) + tmp.setW(1.0f); + tmp /= tmp.w(); + + tmp = tmp * 0.5f + QVector4D(0.5f, 0.5f, 0.5f, 0.5f); + tmp.setX(tmp.x() * viewport.width() + viewport.x()); + tmp.setY(tmp.y() * viewport.height() + viewport.y()); + + return tmp.toVector3D(); +} + +/*! + \since 5.5 + + Returns the object/model coordinates of this vector initially in window + coordinates using the model view matrix \a modelView, the projection matrix + \a projection and the viewport dimensions \a viewport. + + When transforming from clip to normalized space, a division by the w + component of the vector components takes place. To prevent dividing by 0 if + w equals to 0, it is set to 1. + + \note y coordinates in \a point should use OpenGL orientation. OpenGL + expects the bottom to be 0 whereas for Qt top is 0. + + \sa project() + */ +QVector3D QVector3D::unproject(const QMatrix4x4 &modelView, const QMatrix4x4 &projection, const QRect &viewport) const +{ + QMatrix4x4 inverse = QMatrix4x4( projection * modelView ).inverted(); + + QVector4D tmp(*this, 1.0f); + tmp.setX((tmp.x() - float(viewport.x())) / float(viewport.width())); + tmp.setY((tmp.y() - float(viewport.y())) / float(viewport.height())); + tmp = tmp * 2.0f - QVector4D(1.0f, 1.0f, 1.0f, 1.0f); + + QVector4D obj = inverse * tmp; + if (qFuzzyIsNull(obj.w())) + obj.setW(1.0f); + obj /= obj.w(); + return obj.toVector3D(); +} + /*! \since 5.1 diff --git a/src/gui/math3d/qvector3d.h b/src/gui/math3d/qvector3d.h index 40e9035621..4c25451bab 100644 --- a/src/gui/math3d/qvector3d.h +++ b/src/gui/math3d/qvector3d.h @@ -43,6 +43,7 @@ QT_BEGIN_NAMESPACE class QMatrix4x4; class QVector2D; class QVector4D; +class QRect; #ifndef QT_NO_VECTOR3D @@ -94,6 +95,9 @@ public: static QVector3D normal (const QVector3D& v1, const QVector3D& v2, const QVector3D& v3); + QVector3D project(const QMatrix4x4 &modelView, const QMatrix4x4 &projection, const QRect &viewport) const; + QVector3D unproject(const QMatrix4x4 &modelView, const QMatrix4x4 &projection, const QRect &viewport) const; + float distanceToPoint(const QVector3D& point) const; float distanceToPlane(const QVector3D& plane, const QVector3D& normal) const; float distanceToPlane(const QVector3D& plane1, const QVector3D& plane2, const QVector3D& plane3) const; diff --git a/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp b/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp index 2d4b6d16b0..fa5d1b576f 100644 --- a/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp +++ b/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp @@ -36,6 +36,7 @@ #include #include #include +#include class tst_QVectorND : public QObject { @@ -142,6 +143,11 @@ private slots: void dotProduct4_data(); void dotProduct4(); + void project_data(); + void project(); + void unproject_data(); + void unproject(); + void properties(); void metaTypes(); }; @@ -2291,6 +2297,158 @@ void tst_QVectorND::dotProduct4() QCOMPARE(QVector4D::dotProduct(v1, v2), d); } +void tst_QVectorND::project_data() +{ + QTest::addColumn("point"); + QTest::addColumn("viewport"); + QTest::addColumn("projection"); + QTest::addColumn("view"); + QTest::addColumn("result"); + + QMatrix4x4 projection; + projection.ortho(-1.0f, 1.0f, -1.0f, 1.0f, 0.1f, 1000.0f); + + QMatrix4x4 view; + // Located at (0, 0, 10), looking at origin, y is up + view.lookAt(QVector3D(0.0f, 0.0f, 10.0f), QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0f, 1.0f, 0.0f)); + + QMatrix4x4 nullMatrix(0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f); + + QTest::newRow("center") + << QVector3D(0.0f, 0.0f, 0.0f) + << QRect(0.0f, 0.0f, 800.0f, 600.0f) + << projection + << view + << QVector2D(400.0f, 300.0f); + + QTest::newRow("topLeft") + << QVector3D(-1.0f, 1.0f, 0.0f) + << QRect(0.0f, 0.0f, 800.0f, 600.0f) + << projection + << view + << QVector2D(0.0f, 600.0f); + + QTest::newRow("topRight") + << QVector3D(1.0f, 1.0f, 0.0f) + << QRect(0.0f, 0.0f, 800.0f, 600.0f) + << projection + << view + << QVector2D(800.0f, 600.0f); + + QTest::newRow("bottomLeft") + << QVector3D(-1.0f, -1.0f, 0.0f) + << QRect(0.0f, 0.0f, 800.0f, 600.0f) + << projection + << view + << QVector2D(0.0f, 0.0f); + + QTest::newRow("bottomRight") + << QVector3D(1.0f, -1.0f, 0.0f) + << QRect(0.0f, 0.0f, 800.0f, 600.0f) + << projection + << view + << QVector2D(800.0f, 0.0f); + + QTest::newRow("nullMatrix") + << QVector3D(0.0f, 0.0f, 0.0f) + << QRect(0.0f, 0.0f, 800.0f, 600.0f) + << nullMatrix + << nullMatrix + << QVector2D(400.0f, 300.0f); +} + +void tst_QVectorND::project() +{ + QFETCH(QVector3D, point); + QFETCH(QRect, viewport); + QFETCH(QMatrix4x4, projection); + QFETCH(QMatrix4x4, view); + QFETCH(QVector2D, result); + + QVector3D project = point.project(view, projection, viewport); + + QCOMPARE(project.toVector2D(), result); +} + +void tst_QVectorND::unproject_data() +{ + QTest::addColumn("point"); + QTest::addColumn("viewport"); + QTest::addColumn("projection"); + QTest::addColumn("view"); + QTest::addColumn("result"); + + QMatrix4x4 projection; + projection.ortho(-1.0f, 1.0f, -1.0f, 1.0f, 0.1f, 1000.0f); + + QMatrix4x4 view; + // Located at (0, 0, 10), looking at origin, y is up + view.lookAt(QVector3D(0.0f, 0.0f, 10.0f), QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0f, 1.0f, 0.0f)); + + QMatrix4x4 nullMatrix(0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f); + + QTest::newRow("center") + << QVector3D(400.0f, 300.0f, 0.0f) + << QRect(0.0f, 0.0f, 800.0f, 600.0f) + << projection + << view + << QVector3D(0.0f, 0.0f, 9.9f); + + QTest::newRow("topLeft") + << QVector3D(0.0f, 600.0f, 0.0f) + << QRect(0.0f, 0.0f, 800.0f, 600.0f) + << projection + << view + << QVector3D(-1.0f, 1.0f, 9.9f); + + QTest::newRow("topRight") + << QVector3D(800.0f, 600.0f, 0.0f) + << QRect(0.0f, 0.0f, 800.0f, 600.0f) + << projection + << view + << QVector3D(1.0f, 1.0f, 9.9f); + + QTest::newRow("bottomLeft") + << QVector3D(0.0f, 0.0f, 0.0f) + << QRect(0.0f, 0.0f, 800.0f, 600.0f) + << projection + << view + << QVector3D(-1.0, -1.0f, 9.9f); + + QTest::newRow("bottomRight") + << QVector3D(800.0f, 0.0f, 0.0f) + << QRect(0.0f, 0.0f, 800.0f, 600.0f) + << projection + << view + << QVector3D(1.0f, -1.0f, 9.9f); + + QTest::newRow("nullMatrix") + << QVector3D(400.0f, 300.0f, 0.0f) + << QRect(0.0f, 0.0f, 800.0f, 600.0f) + << nullMatrix + << nullMatrix + << QVector3D(0.0f, 0.0f, -1.0f); + +} + +void tst_QVectorND::unproject() +{ + QFETCH(QVector3D, point); + QFETCH(QRect, viewport); + QFETCH(QMatrix4x4, projection); + QFETCH(QMatrix4x4, view); + QFETCH(QVector3D, result); + + QVector3D unproject = point.unproject(view, projection, viewport); + QVERIFY(qFuzzyCompare(unproject, result)); +} + class tst_QVectorNDProperties : public QObject { Q_OBJECT -- cgit v1.2.3