1/****************************************************************************
2**
3** Copyright (C) 2018 Klaralvdalens Datakonsult AB (KDAB).
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt3D module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "qmlscenereader.h"
30#include "testpostmanarbiter.h"
31
32#include <QtTest/QTest>
33#include <Qt3DCore/qentity.h>
34#include <Qt3DCore/qtransform.h>
35#include <Qt3DCore/private/qnodecreatedchangegenerator_p.h>
36#include <Qt3DCore/private/qaspectjobmanager_p.h>
37#include <Qt3DCore/private/qnodevisitor_p.h>
38#include <Qt3DCore/private/qaspectmanager_p.h>
39#include <Qt3DCore/private/qscene_p.h>
40#include <Qt3DCore/private/qaspectengine_p.h>
41#include <Qt3DCore/private/qaspectjob_p.h>
42#include <QtQuick/qquickwindow.h>
43
44#include <Qt3DRender/QCamera>
45#include <Qt3DRender/QRayCaster>
46#include <Qt3DRender/QScreenRayCaster>
47#include <Qt3DRender/private/nodemanagers_p.h>
48#include <Qt3DRender/private/managers_p.h>
49#include <Qt3DRender/private/entity_p.h>
50#include <Qt3DRender/qrenderaspect.h>
51#include <Qt3DRender/private/qrenderaspect_p.h>
52#include <Qt3DRender/private/raycastingjob_p.h>
53#include <Qt3DRender/private/pickboundingvolumeutils_p.h>
54#include <Qt3DRender/private/updatemeshtrianglelistjob_p.h>
55#include <Qt3DRender/private/updateworldboundingvolumejob_p.h>
56#include <Qt3DRender/private/updateworldtransformjob_p.h>
57#include <Qt3DRender/private/expandboundingvolumejob_p.h>
58#include <Qt3DRender/private/calcboundingvolumejob_p.h>
59#include <Qt3DRender/private/calcgeometrytrianglevolumes_p.h>
60#include <Qt3DRender/private/loadbufferjob_p.h>
61#include <Qt3DRender/private/updateentitylayersjob_p.h>
62#include <Qt3DRender/private/buffermanager_p.h>
63#include <Qt3DRender/private/geometryrenderermanager_p.h>
64
65#include <private/qpickevent_p.h>
66
67QT_BEGIN_NAMESPACE
68
69namespace Qt3DRender {
70
71QVector<Qt3DCore::QNode *> getNodesForCreation(Qt3DCore::QNode *root)
72{
73 using namespace Qt3DCore;
74
75 QVector<QNode *> nodes;
76 Qt3DCore::QNodeVisitor visitor;
77 visitor.traverse(rootNode_: root, fN: [&nodes](QNode *node) {
78 nodes.append(t: node);
79
80 // Store the metaobject of the node in the QNode so that we have it available
81 // to us during destruction in the QNode destructor. This allows us to send
82 // the QNodeId and the metaobject as typeinfo to the backend aspects so they
83 // in turn can find the correct QBackendNodeMapper object to handle the destruction
84 // of the corresponding backend nodes.
85 QNodePrivate *d = QNodePrivate::get(q: node);
86 d->m_typeInfo = const_cast<QMetaObject*>(QNodePrivate::findStaticMetaObject(metaObject: node->metaObject()));
87
88 // Mark this node as having been handled for creation so that it is picked up
89 d->m_hasBackendNode = true;
90 });
91
92 return nodes;
93}
94
95QVector<Qt3DCore::NodeTreeChange> nodeTreeChangesForNodes(const QVector<Qt3DCore::QNode *> nodes)
96{
97 QVector<Qt3DCore::NodeTreeChange> nodeTreeChanges;
98 nodeTreeChanges.reserve(size: nodes.size());
99
100 for (Qt3DCore::QNode *n : nodes) {
101 nodeTreeChanges.push_back(t: {
102 .id: n->id(),
103 .metaObj: Qt3DCore::QNodePrivate::get(q: n)->m_typeInfo,
104 .type: Qt3DCore::NodeTreeChange::Added,
105 .node: n
106 });
107 }
108
109 return nodeTreeChanges;
110}
111
112class TestAspect : public Qt3DRender::QRenderAspect
113{
114public:
115 TestAspect(Qt3DCore::QNode *root)
116 : Qt3DRender::QRenderAspect(Qt3DRender::QRenderAspect::Synchronous)
117 , m_sceneRoot(nullptr)
118 {
119 m_engine = new Qt3DCore::QAspectEngine(this);
120 m_engine->registerAspect(aspect: this);
121 Q_ASSERT(d_func()->m_aspectManager);
122
123 // do what QAspectEngine::setRootEntity does since we don't want to enter the simulation loop
124 Qt3DCore::QEntityPtr proot(qobject_cast<Qt3DCore::QEntity *>(object: root), [](Qt3DCore::QEntity *) { });
125 Qt3DCore::QAspectEnginePrivate *aed = Qt3DCore::QAspectEnginePrivate::get(engine: m_engine);
126 aed->m_root = proot;
127 aed->initialize();
128 aed->initNodeTree(node: root);
129 const QVector<Qt3DCore::QNode *> nodes = getNodesForCreation(root);
130 aed->m_aspectManager->setRootEntity(root: proot.data(), nodes);
131
132 Render::Entity *rootEntity = nodeManagers()->lookupResource<Render::Entity, Render::EntityManager>(id: rootEntityId());
133 Q_ASSERT(rootEntity);
134 m_sceneRoot = rootEntity;
135 }
136
137 ~TestAspect()
138 {
139 using namespace Qt3DCore;
140 QNodeVisitor visitor;
141 visitor.traverse(rootNode_: m_engine->rootEntity().data(), fN: [](QNode *node) {
142 QNodePrivate *d = QNodePrivate::get(q: node);
143 d->m_scene = nullptr;
144 d->m_changeArbiter = nullptr;
145 });
146
147 m_engine->unregisterAspect(aspect: this);
148 delete m_engine;
149 m_engine = nullptr;
150 }
151
152 void onRegistered() { QRenderAspect::onRegistered(); }
153 void onUnregistered() { QRenderAspect::onUnregistered(); }
154
155 Qt3DRender::Render::NodeManagers *nodeManagers() const { return d_func()->m_renderer->nodeManagers(); }
156 Qt3DRender::Render::FrameGraphNode *frameGraphRoot() const { return d_func()->m_renderer->frameGraphRoot(); }
157 Qt3DRender::Render::RenderSettings *renderSettings() const { return d_func()->m_renderer->settings(); }
158 Qt3DRender::Render::Entity *sceneRoot() const { return m_sceneRoot; }
159 Qt3DCore::QAspectManager *aspectManager() const { return d_func()->m_aspectManager; }
160 Qt3DCore::QChangeArbiter *arbiter() const { return d_func()->m_arbiter; }
161private:
162 Qt3DCore::QAspectEngine *m_engine;
163 Render::Entity *m_sceneRoot;
164};
165
166} // namespace Qt3DRender
167
168QT_END_NAMESPACE
169
170namespace {
171
172void runRequiredJobs(Qt3DRender::TestAspect *test)
173{
174 QCoreApplication::processEvents();
175 const auto dn = test->arbiter()->takeDirtyFrontEndNodes();
176 Qt3DCore::QAbstractAspectPrivate::get(aspect: test)->syncDirtyFrontEndNodes(nodes: dn);
177
178 Qt3DRender::Render::UpdateWorldTransformJob updateWorldTransform;
179 updateWorldTransform.setRoot(test->sceneRoot());
180 updateWorldTransform.setManagers(test->nodeManagers());
181 updateWorldTransform.run();
182
183 // For each buffer
184 const std::vector<Qt3DRender::Render::HBuffer> &bufferHandles = test->nodeManagers()->bufferManager()->activeHandles();
185 for (auto bufferHandle : bufferHandles) {
186 Qt3DRender::Render::LoadBufferJob loadBuffer(bufferHandle);
187 loadBuffer.setNodeManager(test->nodeManagers());
188 loadBuffer.run();
189 }
190
191 Qt3DRender::Render::CalculateBoundingVolumeJob calcBVolume;
192 calcBVolume.setManagers(test->nodeManagers());
193 calcBVolume.setRoot(test->sceneRoot());
194 calcBVolume.run();
195
196 Qt3DRender::Render::UpdateWorldBoundingVolumeJob updateWorldBVolume;
197 updateWorldBVolume.setManager(test->nodeManagers()->renderNodesManager());
198 updateWorldBVolume.run();
199
200 Qt3DRender::Render::ExpandBoundingVolumeJob expandBVolume;
201 expandBVolume.setRoot(test->sceneRoot());
202 expandBVolume.setManagers(test->nodeManagers());
203 expandBVolume.run();
204
205 Qt3DRender::Render::UpdateMeshTriangleListJob updateTriangleList;
206 updateTriangleList.setManagers(test->nodeManagers());
207 updateTriangleList.run();
208
209 // For each geometry id
210 const std::vector<Qt3DRender::Render::HGeometryRenderer> &geometryRenderHandles = test->nodeManagers()->geometryRendererManager()->activeHandles();
211 for (auto geometryRenderHandle : geometryRenderHandles) {
212 Qt3DCore::QNodeId geometryRendererId = test->nodeManagers()->geometryRendererManager()->data(handle: geometryRenderHandle)->peerId();
213 Qt3DRender::Render::CalcGeometryTriangleVolumes calcGeometryTriangles(geometryRendererId, test->nodeManagers());
214 calcGeometryTriangles.run();
215 }
216
217 Qt3DRender::Render::UpdateEntityLayersJob updateEntityLayer;
218 updateEntityLayer.setManager(test->nodeManagers());
219 updateEntityLayer.run();
220}
221
222void initializeJob(Qt3DRender::Render::RayCastingJob *job, Qt3DRender::TestAspect *test)
223{
224 job->setFrameGraphRoot(test->frameGraphRoot());
225 job->setRoot(test->sceneRoot());
226 job->setManagers(test->nodeManagers());
227 job->setRenderSettings(test->renderSettings());
228}
229
230} // anonymous
231
232class tst_RayCastingJob : public QObject
233{
234 Q_OBJECT
235private Q_SLOTS:
236
237 void worldSpaceRayCaster_data()
238 {
239 QTest::addColumn<QUrl>(name: "source");
240 QTest::addColumn<QVector3D>(name: "rayOrigin");
241 QTest::addColumn<QVector3D>(name: "rayDirection");
242 QTest::addColumn<float>(name: "rayLength");
243 QTest::addColumn<int>(name: "numIntersections");
244
245 QTest::newRow(dataTag: "left entity") << QUrl("qrc:/testscene_worldraycasting.qml") << QVector3D(-5, 0, 4) << QVector3D(0, 0, -1) << 20.f << 1;
246 QTest::newRow(dataTag: "no entity") << QUrl("qrc:/testscene_worldraycasting.qml") << QVector3D(0, 0, 4) << QVector3D(0, 0, -1) << 20.f << 0;
247 QTest::newRow(dataTag: "both entities") << QUrl("qrc:/testscene_worldraycasting.qml") << QVector3D(-8, 0, 0) << QVector3D(1, 0, 0) << 20.f << 2;
248 QTest::newRow(dataTag: "infinite ray") << QUrl("qrc:/testscene_worldraycasting.qml") << QVector3D(-5, 0, 4) << QVector3D(0, 0, -1) << -1.f << 1;
249 QTest::newRow(dataTag: "short ray") << QUrl("qrc:/testscene_worldraycasting.qml") << QVector3D(-5, 0, 4) << QVector3D(0, 0, -1) << 2.f << 0;
250 QTest::newRow(dataTag: "discard filter - right entity") << QUrl("qrc:/testscene_worldraycastinglayer.qml") << QVector3D(5, 0, 4) << QVector3D(0, 0, -1) << 20.f << 0;
251 QTest::newRow(dataTag: "discard filter - both entities") << QUrl("qrc:/testscene_worldraycastinglayer.qml") << QVector3D(-8, 0, 0) << QVector3D(1, 0, 0) << 20.f << 1;
252 QTest::newRow(dataTag: "multi layer - left entity") << QUrl("qrc:/testscene_worldraycastingalllayers.qml") << QVector3D(-5, 0, 4) << QVector3D(0, 0, -1) << 20.f << 0;
253 QTest::newRow(dataTag: "multi layer - right entity") << QUrl("qrc:/testscene_worldraycastingalllayers.qml") << QVector3D(5, 0, 4) << QVector3D(0, 0, -1) << 20.f << 1;
254 QTest::newRow(dataTag: "parent layer") << QUrl("qrc:/testscene_worldraycastingparentlayer.qml") << QVector3D(-8, 0, 0) << QVector3D(1, 0, 0) << 20.f << 1;
255 }
256
257 void worldSpaceRayCaster()
258 {
259 QFETCH(QUrl, source);
260 QFETCH(QVector3D, rayOrigin);
261 QFETCH(QVector3D, rayDirection);
262 QFETCH(float, rayLength);
263 QFETCH(int, numIntersections);
264
265 // GIVEN
266 QmlSceneReader sceneReader(source);
267 QScopedPointer<Qt3DCore::QEntity> root(qobject_cast<Qt3DCore::QEntity *>(object: sceneReader.root()));
268 QVERIFY(root);
269 QScopedPointer<Qt3DRender::TestAspect> test(new Qt3DRender::TestAspect(root.data()));
270
271 Qt3DCore::QComponentVector rootComponents = root->components();
272 Qt3DRender::QRayCaster *rayCaster = nullptr;
273 for (Qt3DCore::QComponent *c: qAsConst(t&: rootComponents)) {
274 rayCaster = qobject_cast<Qt3DRender::QRayCaster *>(object: c);
275 if (rayCaster)
276 break;
277 }
278 QVERIFY(rayCaster);
279
280 rayCaster->trigger(origin: rayOrigin, direction: rayDirection, length: rayLength);
281
282 // Runs Required jobs
283 runRequiredJobs(test: test.data());
284
285 Qt3DRender::Render::RayCaster *backendRayCaster = test->nodeManagers()->rayCasterManager()->lookupResource(id: rayCaster->id());
286 QVERIFY(backendRayCaster);
287 Qt3DCore::QBackendNodePrivate::get(n: backendRayCaster)->setArbiter(test->arbiter());
288
289 // WHEN
290 Qt3DRender::Render::RayCastingJob rayCastingJob;
291 initializeJob(job: &rayCastingJob, test: test.data());
292
293 bool earlyReturn = !rayCastingJob.runHelper();
294 rayCastingJob.postFrame(aspectManager: test->aspectManager());
295 QCoreApplication::processEvents();
296
297 // THEN
298 QVERIFY(!earlyReturn);
299 QVERIFY(!backendRayCaster->isEnabled());
300 QVERIFY(!rayCaster->isEnabled());
301 auto dirtyNodes = test->arbiter()->takeDirtyFrontEndNodes();
302 QCOMPARE(dirtyNodes.count(), 1); // hits & disable
303 QCOMPARE(rayCaster->hits().size(), numIntersections);
304
305 if (numIntersections)
306 QVERIFY(rayCaster->hits().first().entityId());
307 }
308
309 void screenSpaceRayCaster_data()
310 {
311 QTest::addColumn<QUrl>(name: "source");
312 QTest::addColumn<QPoint>(name: "rayPosition");
313 QTest::addColumn<int>(name: "numIntersections");
314
315 QTest::newRow(dataTag: "left entity") << QUrl("qrc:/testscene_screenraycasting.qml") << QPoint(200, 280) << 1;
316 QTest::newRow(dataTag: "no entity") << QUrl("qrc:/testscene_screenraycasting.qml") << QPoint(300, 300) << 0;
317 }
318
319 void screenSpaceRayCaster()
320 {
321 QFETCH(QUrl, source);
322 QFETCH(QPoint, rayPosition);
323 QFETCH(int, numIntersections);
324
325 // GIVEN
326 QmlSceneReader sceneReader(source);
327 QScopedPointer<Qt3DCore::QEntity> root(qobject_cast<Qt3DCore::QEntity *>(object: sceneReader.root()));
328 QVERIFY(root);
329 QScopedPointer<Qt3DRender::TestAspect> test(new Qt3DRender::TestAspect(root.data()));
330
331 Qt3DCore::QComponentVector rootComponents = root->components();
332 Qt3DRender::QScreenRayCaster *rayCaster = nullptr;
333 for (Qt3DCore::QComponent *c: qAsConst(t&: rootComponents)) {
334 rayCaster = qobject_cast<Qt3DRender::QScreenRayCaster *>(object: c);
335 if (rayCaster)
336 break;
337 }
338 QVERIFY(rayCaster);
339
340 rayCaster->trigger(position: rayPosition);
341
342 // Runs Required jobs
343 runRequiredJobs(test: test.data());
344
345 Qt3DRender::Render::RayCaster *backendRayCaster = test->nodeManagers()->rayCasterManager()->lookupResource(id: rayCaster->id());
346 QVERIFY(backendRayCaster);
347 Qt3DCore::QBackendNodePrivate::get(n: backendRayCaster)->setArbiter(test->arbiter());
348
349 // WHEN
350 Qt3DRender::Render::RayCastingJob rayCastingJob;
351 initializeJob(job: &rayCastingJob, test: test.data());
352
353 bool earlyReturn = !rayCastingJob.runHelper();
354 rayCastingJob.postFrame(aspectManager: test->aspectManager());
355 QCoreApplication::processEvents();
356
357 // THEN
358 QVERIFY(!earlyReturn);
359 QVERIFY(!backendRayCaster->isEnabled());
360 QVERIFY(!rayCaster->isEnabled());
361 auto dirtyNodes = test->arbiter()->takeDirtyFrontEndNodes();
362 QCOMPARE(dirtyNodes.count(), 1); // hits & disable
363 QCOMPARE(rayCaster->hits().size(), numIntersections);
364
365 if (numIntersections)
366 QVERIFY(rayCaster->hits().first().entityId());
367 }
368
369};
370
371QTEST_MAIN(tst_RayCastingJob)
372
373#include "tst_raycastingjob.moc"
374

source code of qt3d/tests/auto/render/raycastingjob/tst_raycastingjob.cpp