blob: fae8aa14ea988d4cc56ae1ff96e2e522939b256a [file] [log] [blame]
/****************************************************************************
**
** 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/qaspectjobmanager_p.h>
#include <Qt3DCore/private/qnodevisitor_p.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>
#include <Qt3DRender/private/updateworldtransformjob_p.h>
QT_BEGIN_NAMESPACE
namespace Qt3DRender {
QVector<Qt3DCore::QNode *> getNodesForCreation(Qt3DCore::QNode *root)
{
using namespace Qt3DCore;
QVector<QNode *> nodes;
Qt3DCore::QNodeVisitor visitor;
visitor.traverse(root, [&nodes](QNode *node) {
nodes.append(node);
// Store the metaobject of the node in the QNode so that we have it available
// to us during destruction in the QNode destructor. This allows us to send
// the QNodeId and the metaobject as typeinfo to the backend aspects so they
// in turn can find the correct QBackendNodeMapper object to handle the destruction
// of the corresponding backend nodes.
QNodePrivate *d = QNodePrivate::get(node);
d->m_typeInfo = const_cast<QMetaObject*>(QNodePrivate::findStaticMetaObject(node->metaObject()));
// Mark this node as having been handled for creation so that it is picked up
d->m_hasBackendNode = true;
});
return nodes;
}
QVector<Qt3DCore::NodeTreeChange> nodeTreeChangesForNodes(const QVector<Qt3DCore::QNode *> nodes)
{
QVector<Qt3DCore::NodeTreeChange> nodeTreeChanges;
nodeTreeChanges.reserve(nodes.size());
for (Qt3DCore::QNode *n : nodes) {
nodeTreeChanges.push_back({
n->id(),
Qt3DCore::QNodePrivate::get(n)->m_typeInfo,
Qt3DCore::NodeTreeChange::Added,
n
});
}
return nodeTreeChanges;
}
class TestAspect : public Qt3DRender::QRenderAspect
{
public:
TestAspect(Qt3DCore::QNode *root)
: Qt3DRender::QRenderAspect(Qt3DRender::QRenderAspect::Synchronous)
, m_sceneRoot(nullptr)
{
QRenderAspect::onRegistered();
const QVector<Qt3DCore::QNode *> nodes = getNodesForCreation(root);
d_func()->setRootAndCreateNodes(qobject_cast<Qt3DCore::QEntity *>(root), nodeTreeChangesForNodes(nodes));
Render::Entity *rootEntity = nodeManagers()->lookupResource<Render::Entity, Render::EntityManager>(rootEntityId());
Q_ASSERT(rootEntity);
m_sceneRoot = rootEntity;
}
~TestAspect();
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;
};
TestAspect::~TestAspect()
{
QRenderAspect::onUnregistered();
}
} // namespace Qt3DRender
QT_END_NAMESPACE
namespace {
void runRequiredJobs(Qt3DRender::TestAspect *test)
{
Qt3DRender::Render::UpdateWorldTransformJob updateWorldTransform;
updateWorldTransform.setRoot(test->sceneRoot());
updateWorldTransform.setManagers(test->nodeManagers());
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(convertToQMatrix4x4(*cameraEntity->worldTransform()), convertToQMatrix4x4(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 = convertToQMatrix4x4(*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(convertToQMatrix4x4(*childEntity->worldTransform()), m);
}
};
QTEST_MAIN(tst_QCamera)
#include "tst_qcamera.moc"