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 <sean.harmer@kdab.com>
This commit is contained in:
Paul Lemire 2015-01-19 12:50:05 +01:00 committed by Sean Harmer
parent fdbf3bec30
commit 595ed595ea
3 changed files with 227 additions and 0 deletions

View File

@ -34,10 +34,12 @@
#include "qvector3d.h"
#include "qvector2d.h"
#include "qvector4d.h"
#include "qmatrix4x4.h"
#include <QtCore/qdatastream.h>
#include <QtCore/qmath.h>
#include <QtCore/qvariant.h>
#include <QtCore/qdebug.h>
#include <QtCore/qrect.h>
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

View File

@ -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;

View File

@ -36,6 +36,7 @@
#include <QtGui/qvector2d.h>
#include <QtGui/qvector3d.h>
#include <QtGui/qvector4d.h>
#include <QtGui/qmatrix4x4.h>
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<QVector3D>("point");
QTest::addColumn<QRect>("viewport");
QTest::addColumn<QMatrix4x4>("projection");
QTest::addColumn<QMatrix4x4>("view");
QTest::addColumn<QVector2D>("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<QVector3D>("point");
QTest::addColumn<QRect>("viewport");
QTest::addColumn<QMatrix4x4>("projection");
QTest::addColumn<QMatrix4x4>("view");
QTest::addColumn<QVector3D>("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