blob: eaf85f5b6ea244c5057183f79757c4993518ae11 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2018 Klaralvdalens Datakonsult AB (KDAB).
** 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 "qmlscenereader.h"
#include "testpostmanarbiter.h"
#include <QtTest/QTest>
#include <Qt3DCore/qentity.h>
#include <Qt3DCore/qtransform.h>
#include <Qt3DCore/private/qnodecreatedchangegenerator_p.h>
#include <Qt3DCore/private/qaspectjobmanager_p.h>
#include <Qt3DCore/private/qnodevisitor_p.h>
#include <Qt3DCore/private/qaspectmanager_p.h>
#include <Qt3DCore/private/qscene_p.h>
#include <Qt3DCore/private/qaspectengine_p.h>
#include <QtQuick/qquickwindow.h>
#include <Qt3DRender/QCamera>
#include <Qt3DRender/QRayCaster>
#include <Qt3DRender/QScreenRayCaster>
#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DRender/private/managers_p.h>
#include <Qt3DRender/private/entity_p.h>
#include <Qt3DRender/qrenderaspect.h>
#include <Qt3DRender/private/qrenderaspect_p.h>
#include <Qt3DRender/private/raycastingjob_p.h>
#include <Qt3DRender/private/pickboundingvolumeutils_p.h>
#include <Qt3DRender/private/updatemeshtrianglelistjob_p.h>
#include <Qt3DRender/private/updateworldboundingvolumejob_p.h>
#include <Qt3DRender/private/updateworldtransformjob_p.h>
#include <Qt3DRender/private/expandboundingvolumejob_p.h>
#include <Qt3DRender/private/calcboundingvolumejob_p.h>
#include <Qt3DRender/private/calcgeometrytrianglevolumes_p.h>
#include <Qt3DRender/private/loadbufferjob_p.h>
#include <Qt3DRender/private/buffermanager_p.h>
#include <Qt3DRender/private/geometryrenderermanager_p.h>
#include <private/qpickevent_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)
{
m_engine = new Qt3DCore::QAspectEngine(this);
m_engine->registerAspect(this);
Q_ASSERT(d_func()->m_aspectManager);
// do what QAspectEngine::setRootEntity does since we don't want to enter the simulation loop
Qt3DCore::QEntityPtr proot(qobject_cast<Qt3DCore::QEntity *>(root), [](Qt3DCore::QEntity *) { });
Qt3DCore::QAspectEnginePrivate *aed = Qt3DCore::QAspectEnginePrivate::get(m_engine);
aed->m_root = proot;
aed->initialize();
aed->initNodeTree(root);
const QVector<Qt3DCore::QNode *> nodes = getNodesForCreation(root);
aed->m_aspectManager->setRootEntity(proot.data(), nodes);
Render::Entity *rootEntity = nodeManagers()->lookupResource<Render::Entity, Render::EntityManager>(rootEntityId());
Q_ASSERT(rootEntity);
m_sceneRoot = rootEntity;
}
~TestAspect()
{
using namespace Qt3DCore;
QNodeVisitor visitor;
visitor.traverse(m_engine->rootEntity().data(), [](QNode *node) {
QNodePrivate *d = QNodePrivate::get(node);
d->m_scene = nullptr;
d->m_changeArbiter = nullptr;
});
m_engine->unregisterAspect(this);
delete m_engine;
m_engine = nullptr;
}
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; }
Qt3DCore::QAspectManager *aspectManager() const { return d_func()->m_aspectManager; }
Qt3DCore::QChangeArbiter *arbiter() const { return d_func()->m_arbiter; }
private:
Qt3DCore::QAspectEngine *m_engine;
Render::Entity *m_sceneRoot;
};
} // namespace Qt3DRender
QT_END_NAMESPACE
namespace {
void runRequiredJobs(Qt3DRender::TestAspect *test)
{
QCoreApplication::processEvents();
const auto dn = test->arbiter()->takeDirtyFrontEndNodes();
Qt3DCore::QAbstractAspectPrivate::get(test)->syncDirtyFrontEndNodes(dn);
Qt3DRender::Render::UpdateWorldTransformJob updateWorldTransform;
updateWorldTransform.setRoot(test->sceneRoot());
updateWorldTransform.setManagers(test->nodeManagers());
updateWorldTransform.run();
// For each buffer
QVector<Qt3DRender::Render::HBuffer> bufferHandles = test->nodeManagers()->bufferManager()->activeHandles();
for (auto bufferHandle : bufferHandles) {
Qt3DRender::Render::LoadBufferJob loadBuffer(bufferHandle);
loadBuffer.setNodeManager(test->nodeManagers());
loadBuffer.run();
}
Qt3DRender::Render::CalculateBoundingVolumeJob calcBVolume;
calcBVolume.setManagers(test->nodeManagers());
calcBVolume.setRoot(test->sceneRoot());
calcBVolume.run();
Qt3DRender::Render::UpdateWorldBoundingVolumeJob updateWorldBVolume;
updateWorldBVolume.setManager(test->nodeManagers()->renderNodesManager());
updateWorldBVolume.run();
Qt3DRender::Render::ExpandBoundingVolumeJob expandBVolume;
expandBVolume.setRoot(test->sceneRoot());
expandBVolume.setManagers(test->nodeManagers());
expandBVolume.run();
Qt3DRender::Render::UpdateMeshTriangleListJob updateTriangleList;
updateTriangleList.setManagers(test->nodeManagers());
updateTriangleList.run();
// For each geometry id
QVector<Qt3DRender::Render::HGeometryRenderer> geometryRenderHandles = test->nodeManagers()->geometryRendererManager()->activeHandles();
for (auto geometryRenderHandle : geometryRenderHandles) {
Qt3DCore::QNodeId geometryRendererId = test->nodeManagers()->geometryRendererManager()->data(geometryRenderHandle)->peerId();
Qt3DRender::Render::CalcGeometryTriangleVolumes calcGeometryTriangles(geometryRendererId, test->nodeManagers());
calcGeometryTriangles.run();
}
}
void initializeJob(Qt3DRender::Render::RayCastingJob *job, Qt3DRender::TestAspect *test)
{
job->setFrameGraphRoot(test->frameGraphRoot());
job->setRoot(test->sceneRoot());
job->setManagers(test->nodeManagers());
job->setRenderSettings(test->renderSettings());
}
} // anonymous
class tst_RayCastingJob : public QObject
{
Q_OBJECT
private Q_SLOTS:
void worldSpaceRayCaster_data()
{
QTest::addColumn<QUrl>("source");
QTest::addColumn<QVector3D>("rayOrigin");
QTest::addColumn<QVector3D>("rayDirection");
QTest::addColumn<float>("rayLength");
QTest::addColumn<int>("numIntersections");
QTest::newRow("left entity") << QUrl("qrc:/testscene_worldraycasting.qml") << QVector3D(-5, 0, 4) << QVector3D(0, 0, -1) << 20.f << 1;
QTest::newRow("no entity") << QUrl("qrc:/testscene_worldraycasting.qml") << QVector3D(0, 0, 4) << QVector3D(0, 0, -1) << 20.f << 0;
QTest::newRow("both entities") << QUrl("qrc:/testscene_worldraycasting.qml") << QVector3D(-8, 0, 0) << QVector3D(1, 0, 0) << 20.f << 2;
QTest::newRow("infinite ray") << QUrl("qrc:/testscene_worldraycasting.qml") << QVector3D(-5, 0, 4) << QVector3D(0, 0, -1) << -1.f << 1;
QTest::newRow("short ray") << QUrl("qrc:/testscene_worldraycasting.qml") << QVector3D(-5, 0, 4) << QVector3D(0, 0, -1) << 2.f << 0;
QTest::newRow("discard filter - right entity") << QUrl("qrc:/testscene_worldraycastinglayer.qml") << QVector3D(5, 0, 4) << QVector3D(0, 0, -1) << 20.f << 0;
QTest::newRow("discard filter - both entities") << QUrl("qrc:/testscene_worldraycastinglayer.qml") << QVector3D(-8, 0, 0) << QVector3D(1, 0, 0) << 20.f << 1;
QTest::newRow("multi layer - left entity") << QUrl("qrc:/testscene_worldraycastingalllayers.qml") << QVector3D(-5, 0, 4) << QVector3D(0, 0, -1) << 20.f << 0;
QTest::newRow("multi layer - right entity") << QUrl("qrc:/testscene_worldraycastingalllayers.qml") << QVector3D(5, 0, 4) << QVector3D(0, 0, -1) << 20.f << 1;
QTest::newRow("parent layer") << QUrl("qrc:/testscene_worldraycastingparentlayer.qml") << QVector3D(-8, 0, 0) << QVector3D(1, 0, 0) << 20.f << 1;
}
void worldSpaceRayCaster()
{
QFETCH(QUrl, source);
QFETCH(QVector3D, rayOrigin);
QFETCH(QVector3D, rayDirection);
QFETCH(float, rayLength);
QFETCH(int, numIntersections);
// GIVEN
QmlSceneReader sceneReader(source);
QScopedPointer<Qt3DCore::QEntity> root(qobject_cast<Qt3DCore::QEntity *>(sceneReader.root()));
QVERIFY(root);
QScopedPointer<Qt3DRender::TestAspect> test(new Qt3DRender::TestAspect(root.data()));
Qt3DCore::QComponentVector rootComponents = root->components();
Qt3DRender::QRayCaster *rayCaster = nullptr;
for (Qt3DCore::QComponent *c: qAsConst(rootComponents)) {
rayCaster = qobject_cast<Qt3DRender::QRayCaster *>(c);
if (rayCaster)
break;
}
QVERIFY(rayCaster);
rayCaster->trigger(rayOrigin, rayDirection, rayLength);
// Runs Required jobs
runRequiredJobs(test.data());
Qt3DRender::Render::RayCaster *backendRayCaster = test->nodeManagers()->rayCasterManager()->lookupResource(rayCaster->id());
QVERIFY(backendRayCaster);
Qt3DCore::QBackendNodePrivate::get(backendRayCaster)->setArbiter(test->arbiter());
// WHEN
Qt3DRender::Render::RayCastingJob rayCastingJob;
initializeJob(&rayCastingJob, test.data());
bool earlyReturn = !rayCastingJob.runHelper();
Qt3DCore::QAspectJobPrivate::get(&rayCastingJob)->postFrame(test->aspectManager());
QCoreApplication::processEvents();
// THEN
QVERIFY(!earlyReturn);
QVERIFY(!backendRayCaster->isEnabled());
QVERIFY(!rayCaster->isEnabled());
auto dirtyNodes = test->arbiter()->takeDirtyFrontEndNodes();
QCOMPARE(dirtyNodes.count(), 1); // hits & disable
QCOMPARE(rayCaster->hits().size(), numIntersections);
if (numIntersections)
QVERIFY(rayCaster->hits().first().entityId());
}
void screenSpaceRayCaster_data()
{
QTest::addColumn<QUrl>("source");
QTest::addColumn<QPoint>("rayPosition");
QTest::addColumn<int>("numIntersections");
QTest::newRow("left entity") << QUrl("qrc:/testscene_screenraycasting.qml") << QPoint(200, 280) << 1;
QTest::newRow("no entity") << QUrl("qrc:/testscene_screenraycasting.qml") << QPoint(300, 300) << 0;
}
void screenSpaceRayCaster()
{
QFETCH(QUrl, source);
QFETCH(QPoint, rayPosition);
QFETCH(int, numIntersections);
// GIVEN
QmlSceneReader sceneReader(source);
QScopedPointer<Qt3DCore::QEntity> root(qobject_cast<Qt3DCore::QEntity *>(sceneReader.root()));
QVERIFY(root);
QScopedPointer<Qt3DRender::TestAspect> test(new Qt3DRender::TestAspect(root.data()));
Qt3DCore::QComponentVector rootComponents = root->components();
Qt3DRender::QScreenRayCaster *rayCaster = nullptr;
for (Qt3DCore::QComponent *c: qAsConst(rootComponents)) {
rayCaster = qobject_cast<Qt3DRender::QScreenRayCaster *>(c);
if (rayCaster)
break;
}
QVERIFY(rayCaster);
rayCaster->trigger(rayPosition);
// Runs Required jobs
runRequiredJobs(test.data());
Qt3DRender::Render::RayCaster *backendRayCaster = test->nodeManagers()->rayCasterManager()->lookupResource(rayCaster->id());
QVERIFY(backendRayCaster);
Qt3DCore::QBackendNodePrivate::get(backendRayCaster)->setArbiter(test->arbiter());
// WHEN
Qt3DRender::Render::RayCastingJob rayCastingJob;
initializeJob(&rayCastingJob, test.data());
bool earlyReturn = !rayCastingJob.runHelper();
Qt3DCore::QAspectJobPrivate::get(&rayCastingJob)->postFrame(test->aspectManager());
QCoreApplication::processEvents();
// THEN
QVERIFY(!earlyReturn);
QVERIFY(!backendRayCaster->isEnabled());
QVERIFY(!rayCaster->isEnabled());
auto dirtyNodes = test->arbiter()->takeDirtyFrontEndNodes();
QCOMPARE(dirtyNodes.count(), 1); // hits & disable
QCOMPARE(rayCaster->hits().size(), numIntersections);
if (numIntersections)
QVERIFY(rayCaster->hits().first().entityId());
}
};
QTEST_MAIN(tst_RayCastingJob)
#include "tst_raycastingjob.moc"