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 | |
67 | QT_BEGIN_NAMESPACE |
68 | |
69 | namespace Qt3DRender { |
70 | |
71 | QVector<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 | |
95 | QVector<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 | |
112 | class TestAspect : public Qt3DRender::QRenderAspect |
113 | { |
114 | public: |
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; } |
161 | private: |
162 | Qt3DCore::QAspectEngine *m_engine; |
163 | Render::Entity *m_sceneRoot; |
164 | }; |
165 | |
166 | } // namespace Qt3DRender |
167 | |
168 | QT_END_NAMESPACE |
169 | |
170 | namespace { |
171 | |
172 | void 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 | |
222 | void 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 | |
232 | class tst_RayCastingJob : public QObject |
233 | { |
234 | Q_OBJECT |
235 | private 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 | |
371 | QTEST_MAIN(tst_RayCastingJob) |
372 | |
373 | #include "tst_raycastingjob.moc" |
374 | |