summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSvenn-Arne Dragly <svenn-arne.dragly@qt.io>2018-01-18 09:57:11 +0100
committerSvenn-Arne Dragly <svenn-arne.dragly@qt.io>2018-01-22 09:18:03 +0000
commita2c6dba05776a757dde58f5247f30bd3c0c66d2d (patch)
treefc1dd2f918fc89ecd5544c08eacaf1268d081d53
parent9409dd81287af75108023abbd66e49e07ba65de6 (diff)
downloadqt3d-a2c6dba05776a757dde58f5247f30bd3c0c66d2d.tar.gz
Calculate and store real transform of camera in addition to view matrix
We used to store the viewMatrix as returned by QMatrix4x4::lookAt in the transform component of the QCamera. This was unfortunate for two reasons: 1) An arbitrary entity could not be used as a camera without changing its transform to a viewMatrix, which breaks any other use of the entity. 2) Adding entities as children to cameras lead to the wrong transformation. This made it impossible to properly add objects that should be attached to the camera, such as the hands of a character in first-person view. This commit computes the transform of the camera based on the requested position, view center and up vector. The view matrix is calculated when the matrices are updated in the RenderView. This calculation assumes that the view direction is along the entity's negative z-axis and that the up vector is along the y-axis. Change-Id: If22b29e3d38bf55fbf79e9f7baf0c922e0a48aeb Reviewed-by: Mike Krus <mike.krus@kdab.com> Reviewed-by: Sean Harmer <sean.harmer@kdab.com>
-rw-r--r--src/render/backend/renderview.cpp13
-rw-r--r--src/render/frontend/qcamera.cpp63
-rw-r--r--src/render/frontend/qcamera_p.h8
-rw-r--r--tests/auto/render/qcamera/qcamera.pro12
-rw-r--r--tests/auto/render/qcamera/tst_qcamera.cpp244
-rw-r--r--tests/auto/render/render.pro3
6 files changed, 318 insertions, 25 deletions
diff --git a/src/render/backend/renderview.cpp b/src/render/backend/renderview.cpp
index 341a25e61..4ee92a54a 100644
--- a/src/render/backend/renderview.cpp
+++ b/src/render/backend/renderview.cpp
@@ -716,7 +716,18 @@ QVector<RenderCommand *> RenderView::buildComputeRenderCommands(const QVector<En
void RenderView::updateMatrices()
{
if (m_data.m_renderCameraNode && m_data.m_renderCameraLens && m_data.m_renderCameraLens->isEnabled()) {
- setViewMatrix(*m_data.m_renderCameraNode->worldTransform());
+ const QMatrix4x4 cameraWorld = *(m_data.m_renderCameraNode->worldTransform());
+
+ const QVector4D position = cameraWorld * QVector4D(0.0f, 0.0f, 0.0f, 1.0f);
+ // OpenGL convention is looking down -Z
+ const QVector4D viewDirection = cameraWorld * QVector4D(0.0f, 0.0f, -1.0f, 0.0f);
+ const QVector4D upVector = cameraWorld * QVector4D(0.0f, 1.0f, 0.0f, 0.0f);
+
+ QMatrix4x4 m;
+ m.lookAt(position.toVector3D(), (position + viewDirection).toVector3D(), upVector.toVector3D());
+
+ setViewMatrix(m);
+
setViewProjectionMatrix(m_data.m_renderCameraLens->projection() * viewMatrix());
//To get the eyePosition of the camera, we need to use the inverse of the
//camera's worldTransform matrix.
diff --git a/src/render/frontend/qcamera.cpp b/src/render/frontend/qcamera.cpp
index 38fda277f..f8890ba3d 100644
--- a/src/render/frontend/qcamera.cpp
+++ b/src/render/frontend/qcamera.cpp
@@ -57,7 +57,28 @@ QCameraPrivate::QCameraPrivate()
, m_lens(new QCameraLens())
, m_transform(new Qt3DCore::QTransform())
{
- updateViewMatrix();
+ updateViewMatrixAndTransform(false);
+}
+
+void QCameraPrivate::updateViewMatrixAndTransform(bool doEmit)
+{
+ Q_Q(QCamera);
+
+ const QVector3D viewDirection = (m_viewCenter - m_position).normalized();
+
+ QMatrix4x4 transformMatrix;
+ transformMatrix.translate(m_position);
+
+ // Negative viewDirection because OpenGL convention is looking down -Z
+ transformMatrix.rotate(QQuaternion::fromDirection(-viewDirection, m_upVector.normalized()));
+
+ m_transform->setMatrix(transformMatrix);
+
+ QMatrix4x4 viewMatrix;
+ viewMatrix.lookAt(m_position, m_viewCenter, m_upVector);
+ m_viewMatrix = viewMatrix;
+ if (doEmit)
+ emit q->viewMatrixChanged();
}
/*!
@@ -260,29 +281,34 @@ QCameraPrivate::QCameraPrivate()
/*!
* \qmlproperty vector3d Qt3D.Render::Camera::position
- * Holds the current position of the camera.
+ * Holds the current position of the camera in coordinates relative to
+ * the parent entity.
*/
/*!
* \qmlproperty vector3d Qt3D.Render::Camera::upVector
- * Holds the current up vector of the camera.
+ * Holds the current up vector of the camera in coordinates relative to
+ * the parent entity.
*/
/*!
* \qmlproperty vector3d Qt3D.Render::Camera::viewCenter
- * Holds the current view center of the camera.
+ * Holds the current view center of the camera in coordinates relative to
+ * the parent entity.
* \readonly
*/
/*!
* \qmlproperty vector3d Qt3D.Render::Camera::viewVector
- * Holds the camera's view vector.
+ * Holds the camera's view vector in coordinates relative to
+ * the parent entity.
* \readonly
*/
/*!
* \qmlproperty matrix4x4 Qt3D.Render::Camera::viewMatrix
- * Holds the camera's view matrix.
+ * Holds the camera's view matrix in coordinates relative
+ * to the parent entity.
* \readonly
*/
@@ -352,27 +378,32 @@ QCameraPrivate::QCameraPrivate()
/*!
* \property QCamera::position
- * Holds the camera's position.
+ * Holds the camera's position in coordinates relative to
+ * the parent entity.
*/
/*!
* \property QCamera::upVector
- * Holds the camera's up vector.
+ * Holds the camera's up vector in coordinates relative to
+ * the parent entity.
*/
/*!
* \property QCamera::viewCenter
- * Holds the camera's view center.
+ * Holds the camera's view center in coordinates relative to
+ * the parent entity.
*/
/*!
* \property QCamera::viewVector
- * Holds the camera's view vector.
+ * Holds the camera's view vector in coordinates relative to
+ * the parent entity.
*/
/*!
* \property QCamera::viewMatrix
- * Holds the camera's view matrix.
+ * Holds the camera's view matrix in coordinates relative to
+ * the parent entity.
*/
/*!
@@ -393,7 +424,6 @@ QCamera::QCamera(Qt3DCore::QNode *parent)
QObject::connect(d_func()->m_lens, SIGNAL(topChanged(float)), this, SIGNAL(topChanged(float)));
QObject::connect(d_func()->m_lens, SIGNAL(projectionMatrixChanged(const QMatrix4x4 &)), this, SIGNAL(projectionMatrixChanged(const QMatrix4x4 &)));
QObject::connect(d_func()->m_lens, SIGNAL(exposureChanged(float)), this, SIGNAL(exposureChanged(float)));
- QObject::connect(d_func()->m_transform, SIGNAL(matrixChanged()), this, SIGNAL(viewMatrixChanged()));
addComponent(d_func()->m_lens);
addComponent(d_func()->m_transform);
}
@@ -421,7 +451,6 @@ QCamera::QCamera(QCameraPrivate &dd, Qt3DCore::QNode *parent)
QObject::connect(d_func()->m_lens, SIGNAL(bottomChanged(float)), this, SIGNAL(bottomChanged(float)));
QObject::connect(d_func()->m_lens, SIGNAL(topChanged(float)), this, SIGNAL(topChanged(float)));
QObject::connect(d_func()->m_lens, SIGNAL(projectionMatrixChanged(const QMatrix4x4 &)), this, SIGNAL(projectionMatrixChanged(const QMatrix4x4 &)));
- QObject::connect(d_func()->m_transform, SIGNAL(matrixChanged()), this, SIGNAL(viewMatrixChanged()));
addComponent(d_func()->m_lens);
addComponent(d_func()->m_transform);
}
@@ -812,7 +841,7 @@ void QCamera::setPosition(const QVector3D &position)
d->m_viewMatrixDirty = true;
emit positionChanged(position);
emit viewVectorChanged(d->m_cameraToCenter);
- d->updateViewMatrix();
+ d->updateViewMatrixAndTransform();
}
}
@@ -832,7 +861,7 @@ void QCamera::setUpVector(const QVector3D &upVector)
d->m_upVector = upVector;
d->m_viewMatrixDirty = true;
emit upVectorChanged(upVector);
- d->updateViewMatrix();
+ d->updateViewMatrixAndTransform();
}
}
@@ -854,7 +883,7 @@ void QCamera::setViewCenter(const QVector3D &viewCenter)
d->m_viewMatrixDirty = true;
emit viewCenterChanged(viewCenter);
emit viewVectorChanged(d->m_cameraToCenter);
- d->updateViewMatrix();
+ d->updateViewMatrixAndTransform();
}
}
@@ -873,7 +902,7 @@ QVector3D QCamera::viewVector() const
QMatrix4x4 QCamera::viewMatrix() const
{
Q_D(const QCamera);
- return d->m_transform->matrix();
+ return d->m_viewMatrix;
}
} // Qt3DRender
diff --git a/src/render/frontend/qcamera_p.h b/src/render/frontend/qcamera_p.h
index 2ef53818c..1e8464d05 100644
--- a/src/render/frontend/qcamera_p.h
+++ b/src/render/frontend/qcamera_p.h
@@ -67,12 +67,7 @@ public:
Q_DECLARE_PUBLIC(QCamera)
- void updateViewMatrix()
- {
- QMatrix4x4 m;
- m.lookAt(m_position, m_viewCenter, m_upVector);
- m_transform->setMatrix(m);
- }
+ void updateViewMatrixAndTransform(bool doEmit = true);
QVector3D m_position;
QVector3D m_viewCenter;
@@ -84,6 +79,7 @@ public:
// Components
QCameraLens *m_lens;
Qt3DCore::QTransform *m_transform;
+ QMatrix4x4 m_viewMatrix;
};
} // namespace Qt3DRender
diff --git a/tests/auto/render/qcamera/qcamera.pro b/tests/auto/render/qcamera/qcamera.pro
new file mode 100644
index 000000000..0ce2d23bc
--- /dev/null
+++ b/tests/auto/render/qcamera/qcamera.pro
@@ -0,0 +1,12 @@
+TEMPLATE = app
+
+TARGET = tst_qcamera
+
+QT += 3dcore 3dcore-private 3drender 3drender-private testlib
+
+CONFIG += testcase
+
+SOURCES += tst_qcamera.cpp
+
+include(../../core/common/common.pri)
+include(../commons/commons.pri)
diff --git a/tests/auto/render/qcamera/tst_qcamera.cpp b/tests/auto/render/qcamera/tst_qcamera.cpp
new file mode 100644
index 000000000..82a6d8b89
--- /dev/null
+++ b/tests/auto/render/qcamera/tst_qcamera.cpp
@@ -0,0 +1,244 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include <QtTest/QTest>
+#include <QObject>
+#include <QSignalSpy>
+
+#include <qbackendnodetester.h>
+
+#include <Qt3DCore/QEntity>
+#include <Qt3DCore/private/qnodecreatedchangegenerator_p.h>
+#include <Qt3DCore/private/qaspectjobmanager_p.h>
+#include <Qt3DCore/qpropertyupdatedchange.h>
+#include <Qt3DCore/qnodecreatedchange.h>
+#include <Qt3DCore/qtransform.h>
+
+#include <Qt3DRender/qcamera.h>
+#include <Qt3DRender/qcameralens.h>
+#include <Qt3DRender/qrenderaspect.h>
+#include <Qt3DRender/private/entity_p.h>
+#include <Qt3DRender/private/qcamera_p.h>
+#include <Qt3DRender/private/cameralens_p.h>
+#include <Qt3DRender/private/managers_p.h>
+#include <Qt3DRender/private/nodemanagers_p.h>
+#include <Qt3DRender/private/transform_p.h>
+#include <Qt3DRender/private/qrenderaspect_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DRender {
+
+class TestAspect : public Qt3DRender::QRenderAspect
+{
+public:
+ TestAspect(Qt3DCore::QNode *root)
+ : Qt3DRender::QRenderAspect(Qt3DRender::QRenderAspect::Synchronous)
+ , m_sceneRoot(nullptr)
+ {
+ QRenderAspect::onRegistered();
+
+ const Qt3DCore::QNodeCreatedChangeGenerator generator(root);
+ const QVector<Qt3DCore::QNodeCreatedChangeBasePtr> creationChanges = generator.creationChanges();
+
+ d_func()->setRootAndCreateNodes(qobject_cast<Qt3DCore::QEntity *>(root), creationChanges);
+
+ Render::Entity *rootEntity = nodeManagers()->lookupResource<Render::Entity, Render::EntityManager>(rootEntityId());
+ Q_ASSERT(rootEntity);
+ m_sceneRoot = rootEntity;
+ }
+
+ ~TestAspect()
+ {
+ QRenderAspect::onUnregistered();
+ }
+
+ void onRegistered() { QRenderAspect::onRegistered(); }
+ void onUnregistered() { QRenderAspect::onUnregistered(); }
+
+ Qt3DRender::Render::NodeManagers *nodeManagers() const { return d_func()->m_renderer->nodeManagers(); }
+ Qt3DRender::Render::FrameGraphNode *frameGraphRoot() const { return d_func()->m_renderer->frameGraphRoot(); }
+ Qt3DRender::Render::RenderSettings *renderSettings() const { return d_func()->m_renderer->settings(); }
+ Qt3DRender::Render::Entity *sceneRoot() const { return m_sceneRoot; }
+
+private:
+ Render::Entity *m_sceneRoot;
+};
+
+} // namespace Qt3DRender
+
+QT_END_NAMESPACE
+
+namespace {
+
+void runRequiredJobs(Qt3DRender::TestAspect *test)
+{
+ Qt3DRender::Render::UpdateWorldTransformJob updateWorldTransform;
+ updateWorldTransform.setRoot(test->sceneRoot());
+ updateWorldTransform.run();
+}
+
+void fuzzyCompareMatrix(const QMatrix4x4 &a, const QMatrix4x4 &b)
+{
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ // Fuzzy comparison. qFuzzyCompare is not suitable because zero is compared to small numbers.
+ QVERIFY(qAbs(a(i, j) - b(i, j)) < 1.0e-6f);
+ }
+ }
+}
+
+} // anonymous
+
+class tst_QCamera : public Qt3DCore::QBackendNodeTester
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+
+ void initTestCase()
+ {
+ }
+
+ void checkDefaultConstruction()
+ {
+ // GIVEN
+ Qt3DRender::QCamera camera;
+
+ // THEN
+ QCOMPARE(camera.projectionType(), Qt3DRender::QCameraLens::PerspectiveProjection);
+ QCOMPARE(camera.nearPlane(), 0.1f);
+ QCOMPARE(camera.farPlane(), 1024.0f);
+ QCOMPARE(camera.fieldOfView(), 25.0f);
+ QCOMPARE(camera.aspectRatio(), 1.0f);
+ QCOMPARE(camera.left(), -0.5f);
+ QCOMPARE(camera.right(), 0.5f);
+ QCOMPARE(camera.bottom(), -0.5f);
+ QCOMPARE(camera.top(), 0.5f);
+ QCOMPARE(camera.exposure(), 0.0f);
+ }
+
+ void checkTransformWithParent()
+ {
+ // GIVEN
+ QScopedPointer<Qt3DCore::QEntity> root(new Qt3DCore::QEntity);
+
+ QScopedPointer<Qt3DCore::QEntity> parent(new Qt3DCore::QEntity(root.data()));
+ Qt3DCore::QTransform *parentTransform = new Qt3DCore::QTransform();
+ parentTransform->setTranslation(QVector3D(10, 9, 8));
+ parentTransform->setRotationX(10.f);
+ parentTransform->setRotationY(20.f);
+ parentTransform->setRotationZ(30.f);
+ parent->addComponent(parentTransform);
+
+ QScopedPointer<Qt3DRender::QCamera> camera(new Qt3DRender::QCamera(parent.data()));
+
+ QScopedPointer<Qt3DRender::TestAspect> test(new Qt3DRender::TestAspect(root.data()));
+ runRequiredJobs(test.data());
+
+ Qt3DRender::Render::Entity *cameraEntity = test->nodeManagers()->lookupResource<Qt3DRender::Render::Entity, Qt3DRender::Render::EntityManager>(camera->id());
+
+ // THEN
+ QVERIFY(qFuzzyCompare(*cameraEntity->worldTransform(), parentTransform->matrix()));
+ }
+
+ void checkTransformWithParentAndLookAt()
+ {
+ // GIVEN
+ QScopedPointer<Qt3DCore::QEntity> root(new Qt3DCore::QEntity);
+
+ QScopedPointer<Qt3DCore::QEntity> parent(new Qt3DCore::QEntity(root.data()));
+ Qt3DCore::QTransform *parentTransform = new Qt3DCore::QTransform();
+ parentTransform->setRotationZ(90.f);
+ parent->addComponent(parentTransform);
+
+ QScopedPointer<Qt3DRender::QCamera> camera(new Qt3DRender::QCamera(parent.data()));
+ camera->setPosition(QVector3D(1.f, 0.f, 0.f));
+ camera->setViewCenter(QVector3D(1.f, 0.f, -1.f)); // look in -z
+ camera->setUpVector(QVector3D(0.f, 1.f, 0.f));
+
+ QScopedPointer<Qt3DRender::TestAspect> test(new Qt3DRender::TestAspect(root.data()));
+ runRequiredJobs(test.data());
+
+ Qt3DRender::Render::Entity *cameraEntity = test->nodeManagers()->lookupResource<Qt3DRender::Render::Entity, Qt3DRender::Render::EntityManager>(camera->id());
+
+ // THEN
+ QMatrix4x4 m;
+ m.translate(0.f, 1.f, 0.f); // 90 deg z-rotation + x-translation = y-translation
+ m.rotate(90.f, QVector3D(0.f, 0.f, 1.f));
+
+ const QMatrix4x4 worldTransform = *cameraEntity->worldTransform();
+ fuzzyCompareMatrix(worldTransform, m);
+
+ }
+
+ void checkTransformOfChild()
+ {
+ // GIVEN
+ QScopedPointer<Qt3DCore::QEntity> root(new Qt3DCore::QEntity);
+
+ QScopedPointer<Qt3DCore::QEntity> parent(new Qt3DCore::QEntity(root.data()));
+ Qt3DCore::QTransform *parentTransform = new Qt3DCore::QTransform();
+ parentTransform->setTranslation(QVector3D(10, 9, 8));
+ parent->addComponent(parentTransform);
+
+ QScopedPointer<Qt3DRender::QCamera> camera(new Qt3DRender::QCamera(parent.data()));
+ camera->setPosition(QVector3D(2.f, 3.f, 4.f));
+ camera->setViewCenter(QVector3D(1.f, 3.f, 4.f)); // looking in -x = 90 deg y-rotation
+ camera->setUpVector(QVector3D(0.f, 1.f, 0.f));
+
+ // Child coordinate system:
+ // y = camera up vector = global y
+ // z = negative camera look direction = global x
+ // x = y cross z = global -z
+ QScopedPointer<Qt3DCore::QEntity> child(new Qt3DCore::QEntity(camera.data()));
+ Qt3DCore::QTransform *childTransform = new Qt3DCore::QTransform();
+ childTransform->setTranslation(QVector3D(1.f, 2.f, 3.f));
+ child->addComponent(childTransform);
+
+ QScopedPointer<Qt3DRender::TestAspect> test(new Qt3DRender::TestAspect(root.data()));
+ runRequiredJobs(test.data());
+
+ Qt3DRender::Render::Entity *childEntity = test->nodeManagers()->lookupResource<Qt3DRender::Render::Entity, Qt3DRender::Render::EntityManager>(child->id());
+
+ // THEN
+ QMatrix4x4 m;
+ m.translate(10.f, 9.f, 8.f); // parent's world translation
+ m.translate(2.f, 3.f, 4.f); // camera's world translation
+ m.translate(3.f, 2.f, -1.f); // child's world translation
+ m.rotate(90.f, QVector3D(0.f, 1.f, 0.f)); // camera's rotation
+
+ fuzzyCompareMatrix(*childEntity->worldTransform(), m);
+ }
+};
+
+
+QTEST_MAIN(tst_QCamera)
+
+#include "tst_qcamera.moc"
diff --git a/tests/auto/render/render.pro b/tests/auto/render/render.pro
index 5a82ee835..3f19a9ed3 100644
--- a/tests/auto/render/render.pro
+++ b/tests/auto/render/render.pro
@@ -108,7 +108,8 @@ qtConfig(private_tests) {
qscene2d \
scene2d \
coordinatereader \
- framegraphvisitor
+ framegraphvisitor \
+ qcamera
!macos: SUBDIRS += graphicshelpergl4
}