Revision control

++ // Copyright (C) 2018 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++
++ #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/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 {
++
++ QList<Qt3DCore::QNode *> getNodesForCreation(Qt3DCore::QNode *root)
++ {
++ using namespace Qt3DCore;
++
++ QList<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;
++ }
++
++ QList<Qt3DCore::NodeTreeChange> nodeTreeChangesForNodes(const QList<Qt3DCore::QNode *> nodes)
++ {
++ QList<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::Manual)
++ , m_sceneRoot(nullptr)
++ {
++ QRenderAspect::onRegistered();
++
++ const QList<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() override { QRenderAspect::onRegistered(); }
++ void onUnregistered() override { 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"