/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE namespace Qt3DRender { QVector getNodesForCreation(Qt3DCore::QNode *root) { using namespace Qt3DCore; QVector 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(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 nodeTreeChangesForNodes(const QVector nodes) { QVector 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 nodes = getNodesForCreation(root); d_func()->setRootAndCreateNodes(qobject_cast(root), nodeTreeChangesForNodes(nodes)); Render::Entity *rootEntity = nodeManagers()->lookupResource(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 root(new Qt3DCore::QEntity); QScopedPointer 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 camera(new Qt3DRender::QCamera(parent.data())); QScopedPointer test(new Qt3DRender::TestAspect(root.data())); runRequiredJobs(test.data()); Qt3DRender::Render::Entity *cameraEntity = test->nodeManagers()->lookupResource(camera->id()); // THEN QVERIFY(qFuzzyCompare(convertToQMatrix4x4(*cameraEntity->worldTransform()), convertToQMatrix4x4(parentTransform->matrix()))); } void checkTransformWithParentAndLookAt() { // GIVEN QScopedPointer root(new Qt3DCore::QEntity); QScopedPointer parent(new Qt3DCore::QEntity(root.data())); Qt3DCore::QTransform *parentTransform = new Qt3DCore::QTransform(); parentTransform->setRotationZ(90.f); parent->addComponent(parentTransform); QScopedPointer 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 test(new Qt3DRender::TestAspect(root.data())); runRequiredJobs(test.data()); Qt3DRender::Render::Entity *cameraEntity = test->nodeManagers()->lookupResource(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 root(new Qt3DCore::QEntity); QScopedPointer parent(new Qt3DCore::QEntity(root.data())); Qt3DCore::QTransform *parentTransform = new Qt3DCore::QTransform(); parentTransform->setTranslation(QVector3D(10, 9, 8)); parent->addComponent(parentTransform); QScopedPointer 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 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 test(new Qt3DRender::TestAspect(root.data())); runRequiredJobs(test.data()); Qt3DRender::Render::Entity *childEntity = test->nodeManagers()->lookupResource(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"