Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquick3dviewport.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
20
21#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
22#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
23
24#include <QtQuick3DUtils/private/qssgassert_p.h>
25#include <QtQuick3DUtils/private/qssgfrustum_p.h>
26
27#include <qsgtextureprovider.h>
28#include <QSGSimpleTextureNode>
29#include <QSGRendererInterface>
30#include <QQuickWindow>
31#include <QtQuick/private/qquickitem_p.h>
32#include <QtQuick/private/qquickpointerhandler_p.h>
33
34#include <QtQml>
35
36#include <QtGui/private/qeventpoint_p.h>
37
38#include <QtCore/private/qnumeric_p.h>
39#include <QtCore/qpointer.h>
40
41#include <optional>
42
44
45Q_STATIC_LOGGING_CATEGORY(lcEv, "qt.quick3d.event")
46Q_STATIC_LOGGING_CATEGORY(lcPick, "qt.quick3d.pick")
47
48static bool isforceInputHandlingSet()
49{
50 static const bool v = (qEnvironmentVariableIntValue("QT_QUICK3D_FORCE_INPUT_HANDLING") > 0);
51 return v;
52}
53
55{
56 static void removeAll() {
57 for (auto o : owners) {
58 if (!o.isNull())
59 o->setSceneTransform(nullptr);
60 }
61 owners.clear();
62 }
63
64 void setOnDeliveryAgent(QQuickDeliveryAgent *da) {
65 da->setSceneTransform(this);
66 owners.append(da);
67 }
68
69 /*
70 Transforms viewport coordinates to 2D scene coordinates.
71 Returns the point in targetItem corresponding to \a viewportPoint,
72 assuming that targetItem is mapped onto sceneParentNode.
73 If it's no longer a "hit" on sceneParentNode, returns the last-good point.
74 */
75 QPointF map(const QPointF &viewportPoint) override {
76 QPointF point = viewportPoint;
77 // Despite the name, the input coordinates are the window viewport coordinates
78 // so unless the View3D is the same size of the Window, we need to translate
79 // to the View3D coordinates before doing any picking.
80 if (viewport)
81 point = viewport->mapFromScene(viewportPoint);
82 point.rx() *= scaleX;
83 point.ry() *= scaleY;
84 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(point);
85 if (rayResult.has_value()) {
86 const auto pickResults = renderer->syncPickOne(rayResult.value(), sceneParentNode);
87 if (!pickResults.isEmpty()) {
88 const auto pickResult = pickResults.first();
89 auto ret = pickResult.m_localUVCoords.toPointF();
90 if (!uvCoordsArePixels) {
91 ret = QPointF(targetItem->x() + ret.x() * targetItem->width(),
92 targetItem->y() - ret.y() * targetItem->height() + targetItem->height());
93 }
94 const bool outOfModel = pickResult.m_localUVCoords.isNull();
95 qCDebug(lcEv) << viewportPoint << "->" << (outOfModel ? "OOM" : "") << ret << "@" << pickResult.m_scenePosition
96 << "UV" << pickResult.m_localUVCoords << "dist" << qSqrt(pickResult.m_distanceSq);
97 if (outOfModel) {
98 return lastGoodMapping;
99 } else {
100 lastGoodMapping = ret;
101 return ret;
102 }
103 }
104 }
105 return QPointF();
106 }
107
110 QSSGRenderNode *sceneParentNode = nullptr;
114 bool uvCoordsArePixels = false; // if false, they are in the range 0..1
116
118};
119
121
123{
125public:
126 static void extensionAppend(QQmlListProperty<QQuick3DObject> *list, QQuick3DObject *extension)
127 {
128 QSSG_ASSERT(list && extension, return);
129
130 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
131 if (const auto idx = that->m_extensions.indexOf(extension); idx == -1) {
132 if (!extension->parentItem())
133 extension->setParentItem(that->m_sceneRoot);
134 that->m_extensions.push_back(extension);
135 that->m_extensionListDirty = true;
136 }
137 }
138 }
139 static QQuick3DObject *extensionAt(QQmlListProperty<QQuick3DObject> *list, qsizetype index)
140 {
141 QQuick3DObject *ret = nullptr;
142 QSSG_ASSERT(list, return ret);
143
144 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
145 if (that->m_extensions.size() > index)
146 ret = that->m_extensions.at(index);
147 }
148
149 return ret;
150 }
151 static qsizetype extensionCount(QQmlListProperty<QQuick3DObject> *list)
152 {
153 qsizetype ret = -1;
154 QSSG_ASSERT(list, return ret);
155
156 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object))
157 ret = that->m_extensions.size();
158
159 return ret;
160 }
161 static void extensionClear(QQmlListProperty<QQuick3DObject> *list)
162 {
163 QSSG_ASSERT(list, return);
164
165 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
166 that->m_extensions.clear();
167 that->m_extensionListDirty = true;
168 }
169 }
170 static void extensionReplace(QQmlListProperty<QQuick3DObject> *list, qsizetype idx, QQuick3DObject *o)
171 {
172 QSSG_ASSERT(list, return);
173
174 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
175 if (that->m_extensions.size() > idx && idx > -1) {
176 that->m_extensions.replace(idx, o);
177 that->m_extensionListDirty = true;
178 }
179 }
180 }
181 static void extensionRemoveLast(QQmlListProperty<QQuick3DObject> *list)
182 {
183 QSSG_ASSERT(list, return);
184
185 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
186 that->m_extensions.removeLast();
187 that->m_extensionListDirty = true;
188 }
189 }
190};
191
192/*!
193 \qmltype View3D
194 \inherits Item
195 \inqmlmodule QtQuick3D
196 \brief Provides a viewport on which to render a 3D scene.
197
198 View3D provides a 2D surface on which a 3D scene can be rendered. This
199 surface is a Qt Quick \l Item and can be placed in a Qt Quick scene.
200
201 There are two ways to define the 3D scene that is visualized on the View3D:
202 If you define a hierarchy of \l{Node}{Node-based} items as children of
203 the View3D directly, then this will become the implicit scene of the View3D.
204
205 It is also possible to reference an existing scene by using the \l importScene
206 property and setting it to the root \l Node of the scene you want to visualize.
207 This \l Node does not have to be an ancestor of the View3D, and you can have
208 multiple View3Ds that import the same scene.
209
210 This is demonstrated in \l {Qt Quick 3D - View3D example}{View3D example}.
211
212 If the View3D both has child \l{Node}{Nodes} and the \l importScene property is
213 set simultaneously, then both scenes will be rendered as if they were sibling
214 subtrees in the same scene.
215
216 To control how a scene is rendered, you can set the \l environment
217 property. The type \l SceneEnvironment has a number of visual properties
218 that can be adjusted, such as background color, tone mapping, anti-aliasing
219 and more. \l ExtendedSceneEnvironment in the \c{QtQuick3D.Helpers} module
220 extends \l SceneEnvironment with even more features, adding common
221 post-processing effects.
222
223 In addition, in order for anything to be rendered in the View3D, the scene
224 needs a \l Camera. If there is only a single \l Camera in the scene, then
225 this will automatically be picked. Otherwise, the \l camera property can
226 be used to select the camera. The \l Camera decides which parts of the scene
227 are visible, and how they are projected onto the 2D surface.
228
229 By default, the 3D scene will first be rendered into an off-screen buffer and
230 then composited with the rest of the Qt Quick scene when it is done. This provides
231 the maximum level of compatibility, but may have performance implications on some
232 graphics hardware. If this is the case, the \l renderMode property can be used to
233 switch how the View3D is rendered into the window.
234
235 A View3D with the default Offscreen \l renderMode is implicitly a
236 \l{QSGTextureProvider}{texture provider} as well. This means that \l
237 ShaderEffect or \l{QtQuick3D::Texture::sourceItem}{Texture.sourceItem} can reference
238 the View3D directly as long as all items live within the same
239 \l{QQuickWindow}{window}. Like with any other \l Item, it is also possible
240 to switch the View3D, or one of its ancestors, into a texture-based
241 \l{QtQuick::Item::layer.enabled}{item layer}.
242
243 \sa {Qt Quick 3D - View3D example}
244*/
245
246QQuick3DViewport::QQuick3DViewport(QQuickItem *parent)
247 : QQuickItem(parent)
248{
249 setFlag(ItemHasContents);
250 m_camera = nullptr;
251 m_sceneRoot = new QQuick3DSceneRootNode(this);
252 m_renderStats = new QQuick3DRenderStats();
253 QQuick3DSceneManager *sceneManager = new QQuick3DSceneManager();
254 QQuick3DObjectPrivate::get(m_sceneRoot)->refSceneManager(*sceneManager);
255 Q_ASSERT(sceneManager == QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager);
256 connect(sceneManager, &QQuick3DSceneManager::needsUpdate,
257 this, &QQuickItem::update);
258
259 // Overrides the internal input handling to always be true
260 // instead of potentially updated after a sync (see updatePaintNode)
261 if (isforceInputHandlingSet()) {
262 m_enableInputProcessing = true;
263 updateInputProcessing();
264 forceActiveFocus();
265 }
266}
267
268QQuick3DViewport::~QQuick3DViewport()
269{
270 // With the threaded render loop m_directRenderer must be destroyed on the
271 // render thread at the proper time, not here. That's handled in
272 // releaseResources() + upon sceneGraphInvalidated. However with a
273 // QQuickRenderControl-based window on the main thread there is a good
274 // chance that this viewport (and so our sceneGraphInvalidated signal
275 // connection) is destroyed before the window and the rendercontrol. So act
276 // here then.
277 if (m_directRenderer && m_directRenderer->thread() == thread()) {
278 delete m_directRenderer;
279 m_directRenderer = nullptr;
280 }
281
282 // If the quick window still exists, make sure to disconnect any of the direct
283 // connections to this View3D
284 if (auto qw = window())
285 disconnect(qw, nullptr, this, nullptr);
286
287 auto sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
288 if (sceneManager) {
289 sceneManager->setParent(nullptr);
290 if (auto wa = sceneManager->wattached)
291 wa->queueForCleanup(sceneManager);
292 }
293
294 delete m_sceneRoot;
295 m_sceneRoot = nullptr;
296
297 delete m_builtInEnvironment;
298
299 // m_renderStats is tightly coupled with the render thread, so can't delete while we
300 // might still be rendering.
301 m_renderStats->deleteLater();
302
303 if (!window() && sceneManager && sceneManager->wattached)
304 QMetaObject::invokeMethod(sceneManager->wattached, &QQuick3DWindowAttachment::evaluateEol, Qt::QueuedConnection);
305}
306
307static void ssgn_append(QQmlListProperty<QObject> *property, QObject *obj)
308{
309 if (!obj)
310 return;
311 QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
312
313 if (QQuick3DObject *sceneObject = qmlobject_cast<QQuick3DObject *>(obj)) {
314 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(view3d->scene())->data();
315 itemProperty.append(&itemProperty, sceneObject);
316 } else {
317 QQuickItemPrivate::data_append(property, obj);
318 }
319}
320
321static qsizetype ssgn_count(QQmlListProperty<QObject> *property)
322{
323 QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
324 if (!view3d || !view3d->scene() || !QQuick3DObjectPrivate::get(view3d->scene())->data().count)
325 return 0;
326 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(view3d->scene())->data();
327 return itemProperty.count(&itemProperty);
328}
329
330static QObject *ssgn_at(QQmlListProperty<QObject> *property, qsizetype i)
331{
332 QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
333 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(view3d->scene())->data();
334 return itemProperty.at(&itemProperty, i);
335}
336
337static void ssgn_clear(QQmlListProperty<QObject> *property)
338{
339 QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
340 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(view3d->scene())->data();
341 return itemProperty.clear(&itemProperty);
342}
343
344
345QQmlListProperty<QObject> QQuick3DViewport::data()
346{
347 return QQmlListProperty<QObject>(this,
348 nullptr,
349 ssgn_append,
350 ssgn_count,
351 ssgn_at,
352 ssgn_clear);
353}
354
355/*!
356 \qmlproperty QtQuick3D::Camera QtQuick3D::View3D::camera
357
358 This property specifies which \l Camera is used to render the scene. If this
359 property is not set, then the first enabled camera in the scene will be used.
360
361 \note It is strongly recommended to explicitly set this property and not rely
362 on automatic camera selection. If there are multiple cameras in the scene, automatic
363 camera selection does not provide any guarantees regarding which camera will be selected.
364 If \l{Node::layer}{layers} are used, explicitly setting the camera is required.
365
366 \note If this property contains a camera that's not \l {Node::visible}{visible} then
367 no further attempts to find a camera will be done.
368
369 \sa PerspectiveCamera, OrthographicCamera, FrustumCamera, CustomCamera
370*/
371QQuick3DCamera *QQuick3DViewport::camera() const
372{
373 return m_camera;
374}
375
376/*!
377 \qmlproperty QtQuick3D::SceneEnvironment QtQuick3D::View3D::environment
378
379 This property specifies the SceneEnvironment used to render the scene.
380
381 \note Setting this property to \c null will reset the SceneEnvironment to the default.
382
383 \sa SceneEnvironment
384*/
385QQuick3DSceneEnvironment *QQuick3DViewport::environment() const
386{
387 if (!m_environment) {
388 if (!m_builtInEnvironment) {
389 m_builtInEnvironment = new QQuick3DSceneEnvironment;
390 // Check that we are on the "correct" thread, and move the environment to the
391 // correct thread if not. This can happen when environment() is called from the
392 // sync and no scene environment has been set.
393 if (QThread::currentThread() != m_sceneRoot->thread())
394 m_builtInEnvironment->moveToThread(m_sceneRoot->thread());
395 m_builtInEnvironment->setParentItem(m_sceneRoot);
396 }
397
398 return m_builtInEnvironment;
399 }
400
401 return m_environment;
402}
403
404/*!
405 \qmlproperty QtQuick3D::Node QtQuick3D::View3D::scene
406 \readonly
407
408 Returns the root \l Node of the View3D's scene.
409
410 To define the 3D scene that is visualized in the View3D:
411
412 \list
413 \li Define a hierarchy of \l{Node}{Node-based} items as children of
414 the View3D directly, then this will become the implicit \l scene of the
415 View3D.
416 \li Reference an existing scene by using the \l importScene property and
417 set it to the root \l Node of the scene you want to visualize. This
418 \l Node does not have to be an ancestor of the View3D, and you can have
419 multiple View3Ds that import the same scene.
420 \endlist
421
422 \sa importScene
423*/
424QQuick3DNode *QQuick3DViewport::scene() const
425{
426 return m_sceneRoot;
427}
428
429/*!
430 \qmlproperty QtQuick3D::Node QtQuick3D::View3D::importScene
431
432 This property defines the reference node of the scene to render to the
433 viewport. The node does not have to be a child of the View3D. This
434 referenced node becomes a sibling with child nodes of View3D, if there are
435 any.
436
437 \note Scenes can only be shared between View3D items that are in the same
438 \l{QQuickWindow}{window}.
439
440 \note When sharing scenes between multiple View3D items the imported scene should
441 be imported in whole, that is, importing a subtree of a scene is not supported.
442 If multiple View3Ds need to show different parts of the same shared scene,
443 consider using \l {Node::layer}{layers} instead.
444
445 \note This property can only be set once, and subsequent changes will have
446 no effect.
447
448 You can also define a hierarchy of \l{Node}{Node-based} items as children of
449 the View3D directly, then this will become the implicit scene of the
450 View3D.
451
452 To return the current scene of the View3D, use the \l scene property.
453
454 \sa Node
455*/
456QQuick3DNode *QQuick3DViewport::importScene() const
457{
458 return m_importScene;
459}
460
461/*!
462 \qmlproperty enumeration QtQuick3D::View3D::renderMode
463
464 This property determines how the View3D is combined with the other parts of the
465 Qt Quick scene.
466
467 By default, the scene will be rendered into an off-screen buffer as an intermediate
468 step. This off-screen buffer is then rendered into the window (or render target) like any
469 other Qt Quick \l Item.
470
471 For most users, there will be no need to change the render mode, and this property can
472 safely be ignored. But on some graphics hardware, the use of an off-screen buffer can be
473 a performance bottleneck. If this is the case, it might be worth experimenting with other
474 modes.
475
476 \value View3D.Offscreen The scene is rendered into an off-screen buffer as an intermediate
477 step. This off-screen buffer is then composited with the rest of the Qt Quick scene.
478
479 \value View3D.Underlay The scene is rendered directly to the window before the rest of
480 the Qt Quick scene is rendered. With this mode, the View3D cannot be placed on top of
481 other Qt Quick items.
482
483 \value View3D.Overlay The scene is rendered directly to the window after Qt Quick is
484 rendered. With this mode, the View3D will always be on top of other Qt Quick items.
485
486 \value View3D.Inline The View3D's scene graph is embedded into the main scene graph,
487 and the same ordering semantics are applied as to any other Qt Quick \l Item. As this
488 mode can lead to subtle issues, depending on the contents of the scene, due to
489 injecting depth-based 3D content into a 2D scene graph, it is not recommended to be
490 used, unless a specific need arises.
491
492 The default is \c{View3D.Offscreen}.
493
494 \note When changing the render mode, it is important to note that \c{View3D.Offscreen} (the
495 default) is the only mode which guarantees perfect graphics fidelity. The other modes
496 all have limitations that can cause visual glitches, so it is important to check that
497 the visual output still looks correct when changing this property.
498
499 \note When using the Underlay, Overlay, or Inline modes, it can be useful, and in some
500 cases, required, to disable the Qt Quick scene graph's depth buffer writes via
501 QQuickGraphicsConfiguration::setDepthBufferFor2D() before showing the QQuickWindow or
502 QQuickView hosting the View3D item.
503*/
504QQuick3DViewport::RenderMode QQuick3DViewport::renderMode() const
505{
506 return m_renderMode;
507}
508
509/*!
510 \qmlproperty enumeration QtQuick3D::View3D::renderFormat
511 \since 6.4
512
513 This property determines the backing texture's format. Applicable only when
514 the View3D is rendering to a texture, for example because the \l renderMode
515 is \c{View3D.Offscreen}.
516
517 The default is \c{ShaderEffectSource.RGBA8}.
518
519 If the format is not supported by the underlying graphics driver at run
520 time, RGBA8 is used.
521
522 \list
523 \li ShaderEffectSource.RGBA8
524 \li ShaderEffectSource.RGBA16F
525 \li ShaderEffectSource.RGBA32F
526 \endlist
527
528 \sa QtQuick::ShaderEffectSource::format, QtQuick::Item::layer.format
529 */
530#if QT_CONFIG(quick_shadereffect)
531QQuickShaderEffectSource::Format QQuick3DViewport::renderFormat() const
532{
533 return m_renderFormat;
534}
535#endif
536
537/*!
538 \qmlproperty QtQuick3D::RenderStats QtQuick3D::View3D::renderStats
539 \readonly
540
541 This property provides statistics about the rendering of a frame, such as
542 \l {RenderStats::fps}{fps}, \l {RenderStats::frameTime}{frameTime},
543 \l {RenderStats::renderTime}{renderTime}, \l {RenderStats::syncTime}{syncTime},
544 and \l {RenderStats::maxFrameTime}{maxFrameTime}.
545*/
546QQuick3DRenderStats *QQuick3DViewport::renderStats() const
547{
548 return m_renderStats;
549}
550
551QQuick3DSceneRenderer *QQuick3DViewport::createRenderer() const
552{
553 QQuick3DSceneRenderer *renderer = nullptr;
554
555 if (QQuickWindow *qw = window()) {
556 auto wa = QQuick3DSceneManager::getOrSetWindowAttachment(*qw);
557 auto rci = wa->rci();
558 if (!rci) {
559 QSGRendererInterface *rif = qw->rendererInterface();
560 if (QSSG_GUARD(QSGRendererInterface::isApiRhiBased(rif->graphicsApi()))) {
561 QRhi *rhi = static_cast<QRhi *>(rif->getResource(qw, QSGRendererInterface::RhiResource));
562 QSSG_CHECK_X(rhi != nullptr, "No QRhi from QQuickWindow, this cannot happen");
563 // The RenderContextInterface, and the objects owned by it (such
564 // as, the BufferManager) are always per-QQuickWindow, and so per
565 // scenegraph render thread. Hence the association with window.
566 // Multiple View3Ds in the same window can use the same rendering
567 // infrastructure (so e.g. the same QSSGBufferManager), but two
568 // View3D objects in different windows must not, except for certain
569 // components that do not work with and own native graphics
570 // resources (most notably, QSSGShaderLibraryManager - but this
571 // distinction is handled internally by QSSGRenderContextInterface).
572 rci = std::make_shared<QSSGRenderContextInterface>(rhi);
573 wa->setRci(rci);
574
575 // Use DirectConnection to stay on the render thread, if there is one.
576 connect(wa, &QQuick3DWindowAttachment::releaseCachedResources, this,
577 &QQuick3DViewport::onReleaseCachedResources, Qt::DirectConnection);
578
579 } else {
580 qWarning("The Qt Quick scene is using a rendering method that is not based on QRhi and a 3D graphics API. "
581 "Qt Quick 3D is not functional in such an environment. The View3D item is not going to display anything.");
582 }
583 }
584
585 if (rci) {
586 renderer = new QQuick3DSceneRenderer(rci);
587 Q_QUICK3D_PROFILE_ASSIGN_ID(this, renderer);
588 }
589 }
590
591 return renderer;
592}
593
594bool QQuick3DViewport::isTextureProvider() const
595{
596 // We can only be a texture provider if we are rendering to a texture first
597 if (m_renderMode == QQuick3DViewport::Offscreen)
598 return true;
599
600 return false;
601}
602
603QSGTextureProvider *QQuick3DViewport::textureProvider() const
604{
605 // When Item::layer::enabled == true, QQuickItem will be a texture
606 // provider. In this case we should prefer to return the layer rather
607 // than the fbo texture.
608 if (QQuickItem::isTextureProvider())
609 return QQuickItem::textureProvider();
610
611 // We can only be a texture provider if we are rendering to a texture first
612 if (m_renderMode != QQuick3DViewport::Offscreen)
613 return nullptr;
614
615 QQuickWindow *w = window();
616 if (!w) {
617 qWarning("QSSGView3D::textureProvider: can only be queried on the rendering thread of an exposed window");
618 return nullptr;
619 }
620
621 if (!m_node)
622 m_node = new SGFramebufferObjectNode;
623 return m_node;
624}
625
626class CleanupJob : public QRunnable
627{
628public:
629 CleanupJob(QQuick3DSGDirectRenderer *renderer) : m_renderer(renderer) { }
630 void run() override { delete m_renderer; }
631private:
632 QQuick3DSGDirectRenderer *m_renderer;
633};
634
635void QQuick3DViewport::releaseResources()
636{
637 if (m_directRenderer) {
638 window()->scheduleRenderJob(new CleanupJob(m_directRenderer), QQuickWindow::BeforeSynchronizingStage);
639 m_directRenderer = nullptr;
640 }
641
642 m_node = nullptr;
643}
644
645void QQuick3DViewport::cleanupDirectRenderer()
646{
647 delete m_directRenderer;
648 m_directRenderer = nullptr;
649}
650
651void QQuick3DViewport::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
652{
653 QQuickItem::geometryChange(newGeometry, oldGeometry);
654
655 if (newGeometry.size() != oldGeometry.size())
656 update();
657}
658
659QSGNode *QQuick3DViewport::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *)
660{
661 // When changing render modes
662 if (m_renderModeDirty) {
663 if (node) {
664 delete node;
665 node = nullptr;
666 m_node = nullptr;
667 m_renderNode = nullptr;
668 }
669 if (m_directRenderer) {
670 delete m_directRenderer;
671 m_directRenderer = nullptr;
672 }
673 }
674
675 m_renderModeDirty = false;
676
677 switch (m_renderMode) {
678 // Direct rendering
679 case Underlay:
680 Q_FALLTHROUGH();
681 case Overlay:
682 setupDirectRenderer(m_renderMode);
683 node = nullptr;
684 break;
685 case Offscreen:
686 node = setupOffscreenRenderer(node);
687 break;
688 case Inline:
689 // QSGRenderNode-based rendering
690 node = setupInlineRenderer(node);
691 break;
692 }
693
694 if (!isforceInputHandlingSet()) {
695 // Implicitly enable internal input processing if any item2ds are present.
696 const auto inputHandlingEnabled =
697 QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager->inputHandlingEnabled;
698 const auto enable = inputHandlingEnabled > 0;
699 if (m_enableInputProcessing != enable) {
700 m_enableInputProcessing = enable;
701 QMetaObject::invokeMethod(this, "updateInputProcessing", Qt::QueuedConnection);
702 }
703 }
704
705 return node;
706}
707
708void QQuick3DViewport::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
709{
710 if (change == ItemSceneChange) {
711 if (value.window) {
712 // TODO: if we want to support multiple windows, there has to be a scene manager for
713 // every window.
714 QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager->setWindow(value.window);
715 if (m_importScene)
716 QQuick3DObjectPrivate::get(m_importScene)->sceneManager->setWindow(value.window);
717 m_renderStats->setWindow(value.window);
718 }
719 } else if (change == ItemVisibleHasChanged && isVisible()) {
720 update();
721 }
722}
723
724bool QQuick3DViewport::event(QEvent *event)
725{
726 if (m_enableInputProcessing && event->isPointerEvent())
727 return internalPick(static_cast<QPointerEvent *>(event));
728 else
729 return QQuickItem::event(event);
730}
731
732void QQuick3DViewport::componentComplete()
733{
734 QQuickItem::componentComplete();
735 Q_QUICK3D_PROFILE_REGISTER(this);
736}
737
738void QQuick3DViewport::setCamera(QQuick3DCamera *camera)
739{
740 if (m_camera == camera)
741 return;
742
743 if (camera && !camera->parentItem())
744 camera->setParentItem(m_sceneRoot);
745 if (camera)
746 camera->updateGlobalVariables(QRect(0, 0, width(), height()));
747
748 QQuick3DObjectPrivate::attachWatcherPriv(m_sceneRoot, this, &QQuick3DViewport::setCamera, camera, m_camera);
749
750 m_camera = camera;
751 emit cameraChanged();
752 update();
753}
754
755void QQuick3DViewport::setMultiViewCameras(QQuick3DCamera **firstCamera, int count)
756{
757 m_multiViewCameras.clear();
758 bool sendChangeSignal = false;
759 for (int i = 0; i < count; ++i) {
760 QQuick3DCamera *camera = *(firstCamera + i);
761 if (camera) {
762 if (!camera->parentItem())
763 camera->setParentItem(m_sceneRoot);
764 camera->updateGlobalVariables(QRect(0, 0, width(), height()));
765 }
766 if (i == 0) {
767 if (m_camera != camera) {
768 m_camera = camera;
769 sendChangeSignal = true;
770 }
771 }
772
773 m_multiViewCameras.append(camera);
774
775 // ### do we need attachWatcher stuff? the Xr-provided cameras cannot disappear, although the XrActor (the owner) might
776 }
777
778 if (sendChangeSignal)
779 emit cameraChanged();
780
781 update();
782}
783
784void QQuick3DViewport::setEnvironment(QQuick3DSceneEnvironment *environment)
785{
786 if (m_environment == environment)
787 return;
788
789 m_environment = environment;
790 if (m_environment && !m_environment->parentItem())
791 m_environment->setParentItem(m_sceneRoot);
792
793 QQuick3DObjectPrivate::attachWatcherPriv(m_sceneRoot, this, &QQuick3DViewport::setEnvironment, environment, m_environment);
794
795 emit environmentChanged();
796 update();
797}
798
799void QQuick3DViewport::setImportScene(QQuick3DNode *inScene)
800{
801 // ### We may need consider the case where there is
802 // already a scene tree here
803 // FIXME : Only the first importScene is an effective one
804 if (m_importScene)
805 return;
806
807 // FIXME : Check self-import or cross-import
808 // Currently it does not work since importScene qml parsed in a reverse order.
809 QQuick3DNode *scene = inScene;
810 while (scene) {
811 if (m_sceneRoot == scene) {
812 qmlWarning(this) << "Cannot allow self-import or cross-import!";
813 return;
814 }
815
816 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
817 scene = rn ? rn->view3D()->importScene() : nullptr;
818 }
819
820 m_importScene = inScene;
821 if (m_importScene)
822 updateSceneManagerForImportScene();
823
824 emit importSceneChanged();
825 update();
826}
827
828void QQuick3DViewport::setRenderMode(QQuick3DViewport::RenderMode renderMode)
829{
830 if (m_renderMode == renderMode)
831 return;
832
833 m_renderMode = renderMode;
834 m_renderModeDirty = true;
835 emit renderModeChanged();
836 update();
837}
838
839#if QT_CONFIG(quick_shadereffect)
840void QQuick3DViewport::setRenderFormat(QQuickShaderEffectSource::Format format)
841{
842 if (m_renderFormat == format)
843 return;
844
845 m_renderFormat = format;
846 m_renderModeDirty = true;
847 emit renderFormatChanged();
848 update();
849}
850#endif
851
852/*!
853 \qmlproperty int QtQuick3D::View3D::explicitTextureWidth
854 \since 6.7
855
856 The width, in pixels, of the item's associated texture. Relevant when a
857 fixed texture size is desired that does not depend on the item's size. This
858 size has no effect on the geometry of the item (its size and placement
859 within the scene), which means the texture's content will appear scaled up
860 or down (and possibly stretched) onto the item's area.
861
862 By default the value is \c 0. A value of 0 means that texture's size
863 follows the item's size. (\c{texture size in pixels} = \c{item's logical
864 size} * \c{device pixel ratio}).
865
866 \note This property is relevant only when \l renderMode is set to \c
867 Offscreen. Its value is ignored otherwise.
868
869 \sa explicitTextureHeight, effectiveTextureSize, DebugView
870*/
871int QQuick3DViewport::explicitTextureWidth() const
872{
873 return m_explicitTextureWidth;
874}
875
876void QQuick3DViewport::setExplicitTextureWidth(int width)
877{
878 if (m_explicitTextureWidth == width)
879 return;
880
881 m_explicitTextureWidth = width;
882 emit explicitTextureWidthChanged();
883 update();
884}
885
886/*!
887 \qmlproperty int QtQuick3D::View3D::explicitTextureHeight
888 \since 6.7
889
890 The height, in pixels, of the item's associated texture. Relevant when a
891 fixed texture size is desired that does not depend on the item's size. This
892 size has no effect on the geometry of the item (its size and placement
893 within the scene), which means the texture's content will appear scaled up
894 or down (and possibly stretched) onto the item's area.
895
896 By default the value is \c 0. A value of 0 means that texture's size
897 follows the item's size. (\c{texture size in pixels} = \c{item's logical
898 size} * \c{device pixel ratio}).
899
900 \note This property is relevant only when \l renderMode is set to \c
901 Offscreen. Its value is ignored otherwise.
902
903 \sa explicitTextureWidth, effectiveTextureSize, DebugView
904*/
905int QQuick3DViewport::explicitTextureHeight() const
906{
907 return m_explicitTextureHeight;
908}
909
910void QQuick3DViewport::setExplicitTextureHeight(int height)
911{
912 if (m_explicitTextureHeight == height)
913 return;
914
915 m_explicitTextureHeight = height;
916 emit explicitTextureHeightChanged();
917 update();
918}
919
920/*!
921 \qmlproperty size QtQuick3D::View3D::effectiveTextureSize
922 \since 6.7
923
924 This property exposes the size, in pixels, of the underlying color (and
925 depth/stencil) buffers. It is provided for use on the GUI (main) thread, in
926 QML bindings or JavaScript.
927
928 This is a read-only property.
929
930 \note This property is relevant only when \l renderMode is set to
931 \c Offscreen.
932
933 \sa explicitTextureWidth, explicitTextureHeight, DebugView
934*/
935QSize QQuick3DViewport::effectiveTextureSize() const
936{
937 return m_effectiveTextureSize;
938}
939
940
941/*!
942 \qmlmethod vector3d View3D::mapFrom3DScene(vector3d scenePos)
943
944 Transforms \a scenePos from scene space (3D) into view space (2D).
945
946 The returned x- and y-values will be be in view coordinates, with the top-left
947 corner at [0, 0] and the bottom-right corner at [width, height]. The returned
948 z-value contains the distance from the near clip plane of the frustum (clipNear) to
949 \a scenePos in scene coordinates. If the distance is negative, that means the \a scenePos
950 is behind the active camera. If \a scenePos cannot be mapped to a position in the scene,
951 a position of [0, 0, 0] is returned.
952
953 This function requires that \l camera is assigned to the view.
954
955 \sa mapTo3DScene(), {Camera::mapToViewport()}{Camera.mapToViewport()}
956*/
957QVector3D QQuick3DViewport::mapFrom3DScene(const QVector3D &scenePos) const
958{
959 if (!m_camera) {
960 qmlWarning(this) << "Cannot resolve view position without a camera assigned!";
961 return QVector3D(0, 0, 0);
962 }
963
964 qreal _width = width();
965 qreal _height = height();
966 if (_width == 0 || _height == 0)
967 return QVector3D(0, 0, 0);
968
969 const QVector3D normalizedPos = m_camera->mapToViewport(scenePos, _width, _height);
970 return normalizedPos * QVector3D(float(_width), float(_height), 1);
971}
972
973/*!
974 \qmlmethod vector3d View3D::mapTo3DScene(vector3d viewPos)
975
976 Transforms \a viewPos from view space (2D) into scene space (3D).
977
978 The x- and y-values of \a viewPos should be in view coordinates, with the top-left
979 corner at [0, 0] and the bottom-right corner at [width, height]. The z-value is
980 interpreted as the distance from the near clip plane of the frustum (clipNear) in
981 scene coordinates.
982
983 If \a viewPos cannot successfully be mapped to a position in the scene, a position of
984 [0, 0, 0] is returned.
985
986 This function requires that a \l camera is assigned to the view.
987
988 \sa mapFrom3DScene(), {Camera::mapFromViewport}{Camera.mapFromViewport()}
989*/
990QVector3D QQuick3DViewport::mapTo3DScene(const QVector3D &viewPos) const
991{
992 if (!m_camera) {
993 qmlWarning(this) << "Cannot resolve scene position without a camera assigned!";
994 return QVector3D(0, 0, 0);
995 }
996
997 qreal _width = width();
998 qreal _height = height();
999 if (_width == 0 || _height == 0)
1000 return QVector3D(0, 0, 0);
1001
1002 const QVector3D normalizedPos = viewPos / QVector3D(float(_width), float(_height), 1);
1003 return m_camera->mapFromViewport(normalizedPos, _width, _height);
1004}
1005
1006/*!
1007 \qmlmethod pickResult View3D::pick(float x, float y)
1008
1009 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1010 and return information about the nearest intersection with an object in the scene.
1011
1012 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1013*/
1014QQuick3DPickResult QQuick3DViewport::pick(float x, float y) const
1015{
1016 QQuick3DSceneRenderer *renderer = getRenderer();
1017 if (!renderer)
1018 return QQuick3DPickResult();
1019
1020 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio() * m_widthMultiplier,
1021 qreal(y) * window()->effectiveDevicePixelRatio() * m_heightMultiplier);
1022 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
1023 if (!rayResult.has_value())
1024 return QQuick3DPickResult();
1025
1026 const auto resultList = renderer->syncPick(rayResult.value());
1027 return getNearestPickResult(resultList);
1028}
1029
1030/*!
1031 \qmlmethod pickResult View3D::pick(float x, float y, Model model)
1032
1033 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1034 and return information about the intersection between the ray and the specified \a model.
1035
1036 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1037
1038 \since 6.8
1039*/
1040QQuick3DPickResult QQuick3DViewport::pick(float x, float y, QQuick3DModel *model) const
1041{
1042 QQuick3DSceneRenderer *renderer = getRenderer();
1043 if (!renderer)
1044 return QQuick3DPickResult();
1045
1046 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(),
1047 qreal(y) * window()->effectiveDevicePixelRatio());
1048 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
1049
1050 if (!rayResult.has_value())
1051 return QQuick3DPickResult();
1052
1053 const auto renderNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(model)->spatialNode);
1054 const auto resultList = renderer->syncPickOne(rayResult.value(), renderNode);
1055 return getNearestPickResult(resultList);
1056}
1057
1058/*!
1059 \qmlmethod List<pickResult> View3D::pickSubset(float x, float y, list<Model> models)
1060
1061 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1062 and return information about the intersections with the passed in list of \a models.
1063 This will only check against the list of models passed in.
1064 The returned list is sorted by distance from the camera with the nearest
1065 intersections appearing first and the furthest appearing last.
1066
1067 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1068
1069 Works with both property list<Model> and dynamic JavaScript arrays of models.
1070
1071 \since 6.8
1072*/
1073QList<QQuick3DPickResult> QQuick3DViewport::pickSubset(float x, float y, const QJSValue &models) const
1074{
1075 QQuick3DSceneRenderer *renderer = getRenderer();
1076 if (!renderer)
1077 return {};
1078
1079 QVarLengthArray<QSSGRenderNode*> renderNodes;
1080 // Check for regular JavaScript array
1081 if (models.isArray()) {
1082 const auto length = models.property(QStringLiteral("length")).toInt();
1083 if (length == 0)
1084 return {};
1085
1086 for (int i = 0; i < length; ++i) {
1087 const auto isQObject = models.property(i).isQObject();
1088 if (!isQObject) {
1089 qmlWarning(this) << "Type provided for picking is not a QObject. Needs to be of type QQuick3DModel.";
1090 continue;
1091 }
1092 const auto obj = models.property(i).toQObject();
1093 const auto model = qobject_cast<QQuick3DModel *>(obj);
1094 if (!model) {
1095 qmlWarning(this) << "Type " << obj->metaObject()->className() << " is not supported for picking. Needs to be of type QQuick3DModel.";
1096 continue;
1097 }
1098 const auto priv = QQuick3DObjectPrivate::get(model);
1099 if (priv && priv->spatialNode) {
1100 renderNodes.push_back(static_cast<QSSGRenderNode*>(priv->spatialNode));
1101 }
1102 }
1103 } else {
1104 // Check for property list<Model>
1105 const auto subsetVariant = models.toVariant();
1106 if (!subsetVariant.isValid() || !subsetVariant.canConvert<QQmlListReference>())
1107 return {};
1108
1109 const auto list = subsetVariant.value<QQmlListReference>();
1110
1111 // Only support array of models
1112 if (list.listElementType()->className() != QQuick3DModel::staticMetaObject.className()) {
1113 qmlWarning(this) << "Type " << list.listElementType()->className() << " is not supported for picking. Needs to be of type QQuick3DModel.";
1114 return {};
1115 }
1116 for (int i = 0; i < list.count(); ++i) {
1117 auto model = static_cast<QQuick3DModel *>(list.at(i));
1118 if (!model)
1119 continue;
1120 auto priv = QQuick3DObjectPrivate::get(model);
1121 if (priv && priv->spatialNode) {
1122 renderNodes.push_back(static_cast<QSSGRenderNode*>(priv->spatialNode));
1123 }
1124 }
1125 }
1126
1127 if (renderNodes.empty())
1128 return {};
1129
1130 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(),
1131 qreal(y) * window()->effectiveDevicePixelRatio());
1132 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
1133 if (!rayResult.has_value())
1134 return {};
1135
1136 const auto resultList = renderer->syncPickSubset(rayResult.value(), renderNodes);
1137
1138 QList<QQuick3DPickResult> processedResultList;
1139 processedResultList.reserve(resultList.size());
1140 for (const auto &result : resultList)
1141 processedResultList.append(processPickResult(result));
1142
1143 return processedResultList;
1144}
1145
1146/*!
1147 \qmlmethod List<pickResult> View3D::pickAll(float x, float y)
1148
1149 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1150 and return a list of information about intersections with objects in the scene.
1151 The returned list is sorted by distance from the camera with the nearest
1152 intersections appearing first and the furthest appearing last.
1153
1154 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1155
1156 \since 6.2
1157*/
1158QList<QQuick3DPickResult> QQuick3DViewport::pickAll(float x, float y) const
1159{
1160 QQuick3DSceneRenderer *renderer = getRenderer();
1161 if (!renderer)
1162 return QList<QQuick3DPickResult>();
1163
1164 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio() * m_widthMultiplier,
1165 qreal(y) * window()->effectiveDevicePixelRatio() * m_heightMultiplier);
1166 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
1167 if (!rayResult.has_value())
1168 return QList<QQuick3DPickResult>();
1169
1170 const auto resultList = renderer->syncPickAll(rayResult.value());
1171 QList<QQuick3DPickResult> processedResultList;
1172 processedResultList.reserve(resultList.size());
1173 for (const auto &result : resultList)
1174 processedResultList.append(processPickResult(result));
1175
1176 return processedResultList;
1177}
1178
1179/*!
1180 \qmlmethod pickResult View3D::rayPick(vector3d origin, vector3d direction)
1181
1182 This method will "shoot" a ray into the scene starting at \a origin and in
1183 \a direction and return information about the nearest intersection with an
1184 object in the scene.
1185
1186 This can, for instance, be called with the position and forward vector of
1187 any object in a scene to see what object is in front of an item. This
1188 makes it possible to do picking from any point in the scene.
1189
1190 \since 6.2
1191*/
1192QQuick3DPickResult QQuick3DViewport::rayPick(const QVector3D &origin, const QVector3D &direction) const
1193{
1194 QQuick3DSceneRenderer *renderer = getRenderer();
1195 if (!renderer)
1196 return QQuick3DPickResult();
1197
1198 const QSSGRenderRay ray(origin, direction);
1199 const auto resultList = renderer->syncPick(ray);
1200 return getNearestPickResult(resultList);
1201}
1202
1203/*!
1204 \qmlmethod List<pickResult> View3D::rayPickAll(vector3d origin, vector3d direction)
1205
1206 This method will "shoot" a ray into the scene starting at \a origin and in
1207 \a direction and return a list of information about the nearest intersections with
1208 objects in the scene.
1209 The list is presorted by distance from the origin along the direction
1210 vector with the nearest intersections appearing first and the furthest
1211 appearing last.
1212
1213 This can, for instance, be called with the position and forward vector of
1214 any object in a scene to see what objects are in front of an item. This
1215 makes it possible to do picking from any point in the scene.
1216
1217 \since 6.2
1218*/
1219QList<QQuick3DPickResult> QQuick3DViewport::rayPickAll(const QVector3D &origin, const QVector3D &direction) const
1220{
1221 QQuick3DSceneRenderer *renderer = getRenderer();
1222 if (!renderer)
1223 return QList<QQuick3DPickResult>();
1224
1225 const QSSGRenderRay ray(origin, direction);
1226
1227 const auto resultList = renderer->syncPickAll(ray);
1228 QList<QQuick3DPickResult> processedResultList;
1229 processedResultList.reserve(resultList.size());
1230 for (const auto &result : resultList) {
1231 auto processedResult = processPickResult(result);
1232 if (processedResult.hitType() != QQuick3DPickResultEnums::HitType::Null)
1233 processedResultList.append(processedResult);
1234 }
1235
1236 return processedResultList;
1237}
1238
1239/*!
1240 \qmlmethod pickResult View3D::rayPick(vector3d origin, vector3d direction, Model model)
1241
1242 This method will "shoot" a ray into the scene starting at \a origin and in
1243 \a direction and return information about the intersection between the ray and the specified \a model.
1244
1245 \since 6.11
1246*/
1247QQuick3DPickResult QQuick3DViewport::rayPick(const QVector3D &origin, const QVector3D &direction, QQuick3DModel *model) const
1248{
1249 QQuick3DSceneRenderer *renderer = getRenderer();
1250 if (!renderer)
1251 return QQuick3DPickResult();
1252
1253 const QSSGRenderRay ray(origin, direction);
1254
1255 const auto renderNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(model)->spatialNode);
1256 const auto resultList = renderer->syncPickOne(ray, renderNode);
1257 return getNearestPickResult(resultList);
1258}
1259
1260/*!
1261 \qmlmethod pickResult View3D::closestPointPick(vector3d origin, float radius, Model model)
1262
1263 This method will find the point on the surface of \a model that is nearest to \a origin, within a distance of
1264 \a radius. If \a model is \c null, the closest object within \a radius will be found.
1265
1266 If no such object exists, \c null is returned.
1267
1268 \since 6.11
1269*/
1270QQuick3DPickResult QQuick3DViewport::closestPointPick(const QVector3D &origin, float radius, QQuick3DModel *model) const
1271{
1272 QQuick3DSceneRenderer *renderer = getRenderer();
1273
1274 if (!renderer)
1275 return QQuick3DPickResult();
1276 QSSGRenderNode *renderNode = nullptr;
1277 if (model) {
1278 renderNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(model)->spatialNode);
1279 if (Q_UNLIKELY(!renderNode))
1280 return QQuick3DPickResult{};
1281 }
1282
1283 const auto pickResult = renderer->syncPickClosestPoint(origin, radius * radius, renderNode);
1284 if (!pickResult.has_value())
1285 return QQuick3DPickResult{};
1286 return processPickResult(pickResult.value());
1287}
1288
1289QList<QQuick3DObject *> QQuick3DViewport::pickInRect(const QPointF &start, const QPointF &end) const
1290{
1291 const qreal minX = qMin(start.x(), end.x());
1292 const qreal maxX = qMax(start.x(), end.x());
1293 const qreal minY = qMin(start.y(), end.y());
1294 const qreal maxY = qMax(start.y(), end.y());
1295
1296 const qreal ndc[4] = {
1297 2.0f * minX / width() - 1.0f, // left
1298 2.0f * maxX / width() - 1.0f, // right
1299 // flip Y-coordinates
1300 1.0f - 2.0f * maxY / height(), // bottom
1301 1.0f - 2.0f * minY / height(), // top
1302 };
1303
1304 const float near = 0.0f;
1305 const float far = 1.0f;
1306 enum { L, R, B, T }; // left, right, bottom, top
1307 const QVector4D ndcCorners[8] = {
1308 // Near plane
1309 QVector4D(ndc[L], ndc[B], near, 1.0f), // 0: bottom-left | n0
1310 QVector4D(ndc[R], ndc[B], near, 1.0f), // 1: bottom-right | n1
1311 QVector4D(ndc[L], ndc[T], near, 1.0f), // 3: top-left | n2
1312 QVector4D(ndc[R], ndc[T], near, 1.0f), // 2: top-right | n3
1313 // Far plane
1314 QVector4D(ndc[L], ndc[B], far, 1.0f), // 4: bottom-left | f0
1315 QVector4D(ndc[R], ndc[B], far, 1.0f), // 5: bottom-right | f1
1316 QVector4D(ndc[L], ndc[T], far, 1.0f), // 7: top-left | f2
1317 QVector4D(ndc[R], ndc[T], far, 1.0f), // 6: top-right | f3
1318 };
1319
1320 QMatrix4x4 viewProjection;
1321 if (this->camera()) {
1322 if (auto camera = static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(this->camera())->spatialNode))
1323 camera->calculateViewProjectionMatrix(camera->localTransform, camera->projection, viewProjection);
1324 }
1325 QMatrix4x4 viewProjectionInverted = viewProjection.inverted();
1326
1327 QVector3D worldCorners[8];
1328 for (int i = 0; i < 8; ++i) {
1329 QVector4D worldPoint = viewProjectionInverted * ndcCorners[i];
1330 worldCorners[i] = worldPoint.toVector3D() / worldPoint.w(); // perspective divide
1331 }
1332
1333 // Frustum
1334 //
1335 // f2 ------------f3
1336 // | |
1337 // | |
1338 // | |
1339 // | |
1340 // f0 ------------f1
1341 //
1342 // n2 -------- n3
1343 // | |
1344 // | camera |
1345 // | |
1346 // n0 -------- n1
1347 //
1348 // CCW winding if inside the frustum and looking toward the face
1349 const QSSGFrustum frustum {
1350 QSSGPlane(worldCorners[0], worldCorners[4], worldCorners[6]), // L [n0, f0, f2]
1351 QSSGPlane(worldCorners[1], worldCorners[3], worldCorners[7]), // R [n1, n3, f3]
1352 QSSGPlane(worldCorners[0], worldCorners[1], worldCorners[5]), // B [n0, n1, f1]
1353 QSSGPlane(worldCorners[2], worldCorners[6], worldCorners[7]), // T [n2, f2, f3]
1354 QSSGPlane(worldCorners[0], worldCorners[2], worldCorners[3]), // N [n0, n2, n3]
1355 QSSGPlane(worldCorners[5], worldCorners[7], worldCorners[6]), // F [f1, f3, f2]
1356 };
1357
1358 QList<QQuick3DObject *> ret;
1359 if (QQuick3DSceneRenderer *renderer = getRenderer()) {
1360 auto nodes = renderer->syncPickInFrustum(frustum);
1361 for (auto node : nodes) {
1362 if (QQuick3DObject *m = findFrontendNode(node))
1363 ret.append(m);
1364 }
1365 }
1366
1367 return ret;
1368}
1369
1370void QQuick3DViewport::processPointerEventFromRay(const QVector3D &origin, const QVector3D &direction, QPointerEvent *event) const
1371{
1372 internalPick(event, origin, direction);
1373}
1374
1375// Note: we have enough information to implement Capability::Hover and Capability::ZPosition,
1376// but those properties are not currently available in QTouchEvent/QEventPoint
1377
1378namespace {
1379class SyntheticTouchDevice : public QPointingDevice
1380{
1381public:
1382 SyntheticTouchDevice(QObject *parent = nullptr)
1383 : QPointingDevice(QLatin1StringView("QtQuick3D Touch Synthesizer"),
1384 0,
1385 DeviceType::TouchScreen,
1386 PointerType::Finger,
1387 Capability::Position,
1388 10, 0,
1389 QString(), QPointingDeviceUniqueId(),
1390 parent)
1391 {
1392 }
1393};
1394}
1395
1396/*!
1397 \qmlmethod View3D::setTouchpoint(Item target, point position, int pointId, bool pressed)
1398
1399 Sends a synthetic touch event to \a target, moving the touch point with ID \a pointId to \a position,
1400 with \a pressed determining if the point is pressed.
1401 Also sends the appropriate touch release event if \a pointId was previously active on a different
1402 item.
1403
1404 \since 6.8
1405 */
1406
1407void QQuick3DViewport::setTouchpoint(QQuickItem *target, const QPointF &position, int pointId, bool pressed)
1408{
1409 if (pointId >= m_touchState.size())
1410 m_touchState.resize(pointId + 1);
1411 auto prevState = m_touchState[pointId];
1412
1413 const bool sameTarget = prevState.target == target;
1414 const bool wasPressed = prevState.isPressed;
1415
1416 const bool isPress = pressed && (!sameTarget || !wasPressed);
1417 const bool isRelease = !pressed && wasPressed && sameTarget;
1418
1419 // Hover if we're not active, and we weren't previously active.
1420 // We assume that we always get a non-active for a target when we release.
1421 // This function sends a release events if the target is changed.
1422 if (!sameTarget && wasPressed)
1423 qWarning("QQuick3DViewport::setTouchpoint missing release event");
1424
1425 if (!pressed && !wasPressed) {
1426 // This would be a hover event: skipping
1427 return;
1428 }
1429
1430 m_touchState[pointId] = { target, position, pressed };
1431
1432 if (!m_syntheticTouchDevice)
1433 m_syntheticTouchDevice = new SyntheticTouchDevice(this);
1434
1435 QPointingDevicePrivate *devPriv = QPointingDevicePrivate::get(m_syntheticTouchDevice);
1436
1437 auto makePoint = [devPriv](int id, QEventPoint::State pointState, QPointF pos, quint64 timestamp) -> QEventPoint {
1438 auto epd = devPriv->pointById(id);
1439 auto &ep = epd->eventPoint;
1440 if (pointState != QEventPoint::State::Stationary)
1441 ep.setAccepted(false);
1442
1443 auto res = QMutableEventPoint::withTimeStamp(timestamp, id, pointState, pos, pos, pos);
1444 QMutableEventPoint::update(res, ep);
1445
1446 if (pointState == QEventPoint::State::Pressed)
1447 QMutableEventPoint::setGlobalPressPosition(res, pos);
1448 else if (ep.state() != QEventPoint::State::Unknown)
1449 QMutableEventPoint::setGlobalPressPosition(res, ep.globalPressPosition());
1450
1451 return res;
1452 };
1453
1454 auto sendTouchEvent = [&](QQuickItem *t, const QPointF &position, int pointId, QEventPoint::State pointState, quint64 timestamp) -> void {
1455 QList<QEventPoint> points;
1456 bool otherPoint = false; // Does the event have another point already?
1457 for (int i = 0; i < m_touchState.size(); ++i) {
1458 const auto &ts = m_touchState[i];
1459 if (ts.target != t)
1460 continue;
1461 if (i == pointId) {
1462 auto newPoint = makePoint(i, pointState, position, timestamp);
1463 points << newPoint;
1464 } else if (ts.isPressed) {
1465 otherPoint = true;
1466 points << makePoint(i, QEventPoint::Stationary, ts.position, timestamp);
1467 }
1468 }
1469
1470 QEvent::Type type;
1471 if (pointState == QEventPoint::Pressed && !otherPoint)
1472 type = QEvent::Type::TouchBegin;
1473 else if (pointState == QEventPoint::Released && !otherPoint)
1474 type = QEvent::Type::TouchEnd;
1475 else
1476 type = QEvent::Type::TouchUpdate;
1477
1478 QTouchEvent ev(type, m_syntheticTouchDevice, {}, points);
1479 ev.setTimestamp(timestamp);
1480
1481 if (t) {
1482 // Actually send event:
1483 auto da = QQuickItemPrivate::get(t)->deliveryAgent();
1484 bool handled = da->event(&ev);
1485 Q_UNUSED(handled);
1486 }
1487
1488 // Duplicate logic from QQuickWindowPrivate::clearGrabbers
1489 if (ev.isEndEvent()) {
1490 for (auto &point : ev.points()) {
1491 if (point.state() == QEventPoint::State::Released) {
1492 ev.setExclusiveGrabber(point, nullptr);
1493 ev.clearPassiveGrabbers(point);
1494 }
1495 }
1496 }
1497 };
1498
1499 auto timestamp = QDateTime::currentMSecsSinceEpoch();
1500
1501 // Send a release event to the previous target
1502 if (prevState.target && !sameTarget)
1503 sendTouchEvent(prevState.target, prevState.position, pointId, QEventPoint::Released, timestamp);
1504
1505 // Now send an event for the new state
1506 QEventPoint::State newState = isPress ? QEventPoint::Pressed : isRelease ? QEventPoint::Released : QEventPoint::Updated;
1507 sendTouchEvent(target, position, pointId, newState, timestamp);
1508}
1509
1510QQuick3DLightmapBaker *QQuick3DViewport::maybeLightmapBaker()
1511{
1512 return m_lightmapBaker;
1513}
1514
1515QQuick3DLightmapBaker *QQuick3DViewport::lightmapBaker()
1516{
1517 if (!m_lightmapBaker)
1518 m_lightmapBaker= new QQuick3DLightmapBaker(this);
1519
1520 return m_lightmapBaker;
1521}
1522
1523/*!
1524 \internal
1525*/
1526void QQuick3DViewport::bakeLightmap()
1527{
1528 QQuick3DSceneRenderer *renderer = getRenderer();
1529 if (!renderer || !renderer->m_layer->renderData)
1530 return;
1531
1532 const bool currentlyBaking = renderer->m_layer->renderData->lightmapBaker != nullptr;
1533
1534 if (!currentlyBaking)
1535 lightmapBaker()->bake();
1536}
1537
1538/*!
1539 \internal
1540*/
1541void QQuick3DViewport::denoiseLightmap()
1542{
1543 QQuick3DSceneRenderer *renderer = getRenderer();
1544 if (!renderer || !renderer->m_layer->renderData)
1545 return;
1546
1547 const bool currentlyBaking = renderer->m_layer->renderData->lightmapBaker != nullptr;
1548
1549 if (!currentlyBaking)
1550 lightmapBaker()->denoise();
1551}
1552
1553
1554void QQuick3DViewport::setGlobalPickingEnabled(bool isEnabled)
1555{
1556 QQuick3DSceneRenderer *renderer = getRenderer();
1557 if (!renderer)
1558 return;
1559
1560 renderer->setGlobalPickingEnabled(isEnabled);
1561}
1562
1563void QQuick3DViewport::invalidateSceneGraph()
1564{
1565 m_node = nullptr;
1566}
1567
1568QQuick3DSceneRenderer *QQuick3DViewport::getRenderer() const
1569{
1570 QQuick3DSceneRenderer *renderer = nullptr;
1571 if (m_node) {
1572 renderer = m_node->renderer;
1573 } else if (m_renderNode) {
1574 renderer = m_renderNode->renderer;
1575 } else if (m_directRenderer) {
1576 renderer = m_directRenderer->renderer();
1577 }
1578 return renderer;
1579}
1580
1581void QQuick3DViewport::updateDynamicTextures()
1582{
1583 // Update QSGDynamicTextures that are used for source textures and Quick items
1584 // Must be called on the render thread.
1585
1586 const auto &sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
1587 for (auto *texture : std::as_const(sceneManager->qsgDynamicTextures))
1588 texture->updateTexture();
1589
1590 QQuick3DNode *scene = m_importScene;
1591 while (scene) {
1592 const auto &importSm = QQuick3DObjectPrivate::get(scene)->sceneManager;
1593 if (importSm != sceneManager) {
1594 for (auto *texture : std::as_const(importSm->qsgDynamicTextures))
1595 texture->updateTexture();
1596 }
1597
1598 // if importScene has another import
1599 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
1600 scene = rn ? rn->view3D()->importScene() : nullptr;
1601 }
1602}
1603
1604QSGNode *QQuick3DViewport::setupOffscreenRenderer(QSGNode *node)
1605{
1606 SGFramebufferObjectNode *n = static_cast<SGFramebufferObjectNode *>(node);
1607
1608 if (!n) {
1609 if (!m_node)
1610 m_node = new SGFramebufferObjectNode;
1611 n = m_node;
1612 }
1613
1614 if (!n->renderer) {
1615 n->window = window();
1616 n->renderer = createRenderer();
1617 if (!n->renderer)
1618 return nullptr;
1619 n->renderer->fboNode = n;
1620 n->quickFbo = this;
1621 connect(window(), SIGNAL(screenChanged(QScreen*)), n, SLOT(handleScreenChange()));
1622 }
1623
1624 const qreal dpr = window()->effectiveDevicePixelRatio();
1625 const QSize minFboSize = QQuickItemPrivate::get(this)->sceneGraphContext()->minimumFBOSize();
1626 QSize desiredFboSize = QSize(m_explicitTextureWidth, m_explicitTextureHeight);
1627 if (desiredFboSize.isEmpty()) {
1628 desiredFboSize = QSize(width(), height()) * dpr;
1629 n->devicePixelRatio = dpr;
1630 // 1:1 mapping between the backing texture and the on-screen quad
1631 m_widthMultiplier = 1.0f;
1632 m_heightMultiplier = 1.0f;
1633 } else {
1634 QSize itemPixelSize = QSize(width(), height()) * dpr;
1635 // not 1:1 maping between the backing texture and the on-screen quad
1636 m_widthMultiplier = desiredFboSize.width() / float(itemPixelSize.width());
1637 m_heightMultiplier = desiredFboSize.height() / float(itemPixelSize.height());
1638 n->devicePixelRatio = 1.0;
1639 }
1640 desiredFboSize.setWidth(qMax(minFboSize.width(), desiredFboSize.width()));
1641 desiredFboSize.setHeight(qMax(minFboSize.height(), desiredFboSize.height()));
1642
1643 if (desiredFboSize != m_effectiveTextureSize) {
1644 m_effectiveTextureSize = desiredFboSize;
1645 emit effectiveTextureSizeChanged();
1646 }
1647
1648 n->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
1649 n->setRect(0, 0, width(), height());
1650 if (checkIsVisible() && isComponentComplete()) {
1651 n->renderer->synchronize(this, desiredFboSize, n->devicePixelRatio);
1652 if (n->renderer->m_textureNeedsFlip)
1653 n->setTextureCoordinatesTransform(QSGSimpleTextureNode::MirrorVertically);
1654 updateDynamicTextures();
1655 n->scheduleRender();
1656 }
1657
1658 return n;
1659}
1660
1661QSGNode *QQuick3DViewport::setupInlineRenderer(QSGNode *node)
1662{
1663 QQuick3DSGRenderNode *n = static_cast<QQuick3DSGRenderNode *>(node);
1664 if (!n) {
1665 if (!m_renderNode)
1666 m_renderNode = new QQuick3DSGRenderNode;
1667 n = m_renderNode;
1668 }
1669
1670 if (!n->renderer) {
1671 n->window = window();
1672 n->renderer = createRenderer();
1673 if (!n->renderer)
1674 return nullptr;
1675 }
1676
1677 if (!m_effectiveTextureSize.isEmpty()) {
1678 m_effectiveTextureSize = QSize();
1679 emit effectiveTextureSizeChanged();
1680 }
1681
1682 const QSize targetSize = window()->effectiveDevicePixelRatio() * QSize(width(), height());
1683
1684 // checkIsVisible, not isVisible, because, for example, a
1685 // { visible: false; layer.enabled: true } item still needs
1686 // to function normally.
1687 if (checkIsVisible() && isComponentComplete()) {
1688 n->renderer->synchronize(this, targetSize, window()->effectiveDevicePixelRatio());
1689 updateDynamicTextures();
1690 n->markDirty(QSGNode::DirtyMaterial);
1691 }
1692
1693 return n;
1694}
1695
1696
1697void QQuick3DViewport::setupDirectRenderer(RenderMode mode)
1698{
1699 auto renderMode = (mode == Underlay) ? QQuick3DSGDirectRenderer::Underlay
1700 : QQuick3DSGDirectRenderer::Overlay;
1701 if (!m_directRenderer) {
1702 QQuick3DSceneRenderer *sceneRenderer = createRenderer();
1703 if (!sceneRenderer)
1704 return;
1705 m_directRenderer = new QQuick3DSGDirectRenderer(sceneRenderer, window(), renderMode);
1706 connect(window(), &QQuickWindow::sceneGraphInvalidated, this, &QQuick3DViewport::cleanupDirectRenderer, Qt::DirectConnection);
1707 }
1708
1709 if (!m_effectiveTextureSize.isEmpty()) {
1710 m_effectiveTextureSize = QSize();
1711 emit effectiveTextureSizeChanged();
1712 }
1713
1714 const QSizeF targetSize = window()->effectiveDevicePixelRatio() * QSizeF(width(), height());
1715 m_directRenderer->setViewport(QRectF(window()->effectiveDevicePixelRatio() * mapToScene(QPointF(0, 0)), targetSize));
1716 m_directRenderer->setVisibility(isVisible());
1717 if (isVisible()) {
1718 m_directRenderer->preSynchronize();
1719 m_directRenderer->renderer()->synchronize(this, targetSize.toSize(), window()->effectiveDevicePixelRatio());
1720 updateDynamicTextures();
1721 m_directRenderer->requestRender();
1722 }
1723}
1724
1725// This is used for offscreen mode since we need to check if
1726// this item is used by an effect but hidden
1727bool QQuick3DViewport::checkIsVisible() const
1728{
1729 auto childPrivate = QQuickItemPrivate::get(this);
1730 return (childPrivate->explicitVisible ||
1731 (childPrivate->extra.isAllocated() && childPrivate->extra->effectRefCount));
1732
1733}
1734
1735/*!
1736 \internal
1737
1738 This method processes a pickResult on an object with the objective of checking
1739 if there are any QQuickItem based subscenes that the QPointerEvent needs to be
1740 forwarded to (3D -> 2D). If such a subscene is found, the event will be mapped
1741 to the correct cordinate system, and added to the visitedSubscenes map for later
1742 event delivery.
1743*/
1744void QQuick3DViewport::processPickedObject(const QSSGRenderPickResult &pickResult,
1745 int pointIndex,
1746 QPointerEvent *event,
1747 QFlatMap<QQuickItem *, SubsceneInfo> &visitedSubscenes) const
1748{
1749 QQuickItem *subsceneRootItem = nullptr;
1750 QPointF subscenePosition;
1751 const auto backendObject = pickResult.m_hitObject;
1752 const auto frontendObject = findFrontendNode(backendObject);
1753 if (!frontendObject)
1754 return;
1755
1756 // Figure out if there are any QQuickItem based scenes we need to forward
1757 // the event to, and if the are found, determine how to translate the UV Coords
1758 // in the pickResult based on what type the object containing the scene is.
1759 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(frontendObject);
1760 if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
1761 // Item2D, this is the case where there is just an embedded Qt Quick 2D Item
1762 // rendered directly to the scene.
1763 auto item2D = qobject_cast<QQuick3DItem2D *>(frontendObject);
1764 if (item2D)
1765 subsceneRootItem = item2D->contentItem();
1766 if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
1767 return; // ignore empty 2D subscenes
1768
1769 // In this case the "UV" coordinates are in pixels in the subscene root item's coordinate system.
1770 subscenePosition = pickResult.m_localUVCoords.toPointF();
1771
1772 // The following code will account for custom input masking, as well any
1773 // transformations that might have been applied to the Item
1774 if (!subsceneRootItem->childAt(subscenePosition.x(), subscenePosition.y()))
1775 return;
1776 } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
1777 // Model
1778 int materialSubset = pickResult.m_subset;
1779 const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
1780 // Get material
1781 if (backendModel->materials.size() < (pickResult.m_subset + 1))
1782 materialSubset = backendModel->materials.size() - 1;
1783 if (materialSubset < 0)
1784 return;
1785 const auto backendMaterial = backendModel->materials.at(materialSubset);
1786 const auto frontendMaterial = static_cast<QQuick3DMaterial*>(findFrontendNode(backendMaterial));
1787 subsceneRootItem = getSubSceneRootItem(frontendMaterial);
1788
1789 if (subsceneRootItem) {
1790 // In this case the pick result really is using UV coordinates.
1791 subscenePosition = QPointF(subsceneRootItem->x() + pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
1792 subsceneRootItem->y() - pickResult.m_localUVCoords.y() * subsceneRootItem->height() + subsceneRootItem->height());
1793 }
1794 }
1795
1796 // Add the new event (item and position) to the visitedSubscene map.
1797 if (subsceneRootItem) {
1798 SubsceneInfo &subscene = visitedSubscenes[subsceneRootItem]; // create if not found
1799 subscene.obj = frontendObject;
1800 if (subscene.eventPointScenePositions.size() != event->pointCount()) {
1801 // ensure capacity, and use an out-of-scene position rather than 0,0 by default
1802 constexpr QPointF inf(-qt_inf(), -qt_inf());
1803 subscene.eventPointScenePositions.resize(event->pointCount(), inf);
1804 }
1805 subscene.eventPointScenePositions[pointIndex] = subscenePosition;
1806 }
1807}
1808
1809/*!
1810 \internal
1811
1812 This method will try and find a QQuickItem based by looking for sourceItem()
1813 properties on the primary color channels for the various material types.
1814
1815 \note for CustomMaterial there is just a best effort given where the first
1816 associated Texture with a sourceItem property is used.
1817*/
1818
1819QQuickItem *QQuick3DViewport::getSubSceneRootItem(QQuick3DMaterial *material) const
1820{
1821 if (!material)
1822 return nullptr;
1823
1824 QQuickItem *subsceneRootItem = nullptr;
1825 const auto frontendMaterialPrivate = QQuick3DObjectPrivate::get(material);
1826
1827 if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::DefaultMaterial) {
1828 // Default Material
1829 const auto defaultMaterial = qobject_cast<QQuick3DDefaultMaterial *>(material);
1830 if (defaultMaterial) {
1831 // Just check for a diffuseMap for now
1832 if (defaultMaterial->diffuseMap() && defaultMaterial->diffuseMap()->sourceItem())
1833 subsceneRootItem = defaultMaterial->diffuseMap()->sourceItem();
1834 }
1835
1836 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::PrincipledMaterial) {
1837 // Principled Material
1838 const auto principledMaterial = qobject_cast<QQuick3DPrincipledMaterial *>(material);
1839 if (principledMaterial) {
1840 // Just check for a baseColorMap for now
1841 if (principledMaterial->baseColorMap() && principledMaterial->baseColorMap()->sourceItem())
1842 subsceneRootItem = principledMaterial->baseColorMap()->sourceItem();
1843 }
1844 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::SpecularGlossyMaterial) {
1845 // SpecularGlossy Material
1846 const auto specularGlossyMaterial = qobject_cast<QQuick3DSpecularGlossyMaterial *>(material);
1847 if (specularGlossyMaterial) {
1848 // Just check for a albedoMap for now
1849 if (specularGlossyMaterial->albedoMap() && specularGlossyMaterial->albedoMap()->sourceItem())
1850 subsceneRootItem = specularGlossyMaterial->albedoMap()->sourceItem();
1851 }
1852 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::CustomMaterial) {
1853 // Custom Material
1854 const auto customMaterial = qobject_cast<QQuick3DCustomMaterial *>(material);
1855 if (customMaterial) {
1856 // This case is a bit harder because we can not know how the textures will be used
1857 const auto &texturesInputs = customMaterial->m_dynamicTextureMaps;
1858 for (const auto &textureInput : texturesInputs) {
1859 if (auto texture = textureInput->texture()) {
1860 if (texture->sourceItem()) {
1861 subsceneRootItem = texture->sourceItem();
1862 break;
1863 }
1864 }
1865 }
1866 }
1867 }
1868 return subsceneRootItem;
1869}
1870
1871
1872/*!
1873 \internal
1874*/
1875QQuick3DPickResult QQuick3DViewport::getNearestPickResult(const QVarLengthArray<QSSGRenderPickResult, 20> &pickResults) const
1876{
1877 for (const auto &result : pickResults) {
1878 auto pickResult = processPickResult(result);
1879 if (pickResult.hitType() != QQuick3DPickResultEnums::HitType::Null)
1880 return pickResult;
1881 }
1882
1883 return QQuick3DPickResult();
1884}
1885
1886/*!
1887 \internal
1888 This method is responsible for going through the visitedSubscenes map and forwarding
1889 the event with corrected coordinates to each subscene.
1890
1891*/
1892bool QQuick3DViewport::forwardEventToSubscenes(QPointerEvent *event,
1893 bool useRayPicking,
1894 QQuick3DSceneRenderer *renderer,
1895 const QFlatMap<QQuickItem *, SubsceneInfo> &visitedSubscenes) const
1896{
1897 // Now deliver the entire event (all points) to each relevant subscene.
1898 // Maybe only some points fall inside, but QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem()
1899 // makes reduced-subset touch events that contain only the relevant points, when necessary.
1900 bool ret = false;
1901
1902 QVarLengthArray<QPointF, 16> originalScenePositions;
1903 originalScenePositions.resize(event->pointCount());
1904 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
1905 originalScenePositions[pointIndex] = event->point(pointIndex).scenePosition();
1906 for (auto subscene : visitedSubscenes) {
1907 QQuickItem *subsceneRoot = subscene.first;
1908 auto &subsceneInfo = subscene.second;
1909 Q_ASSERT(subsceneInfo.eventPointScenePositions.size() == event->pointCount());
1910 auto da = QQuickItemPrivate::get(subsceneRoot)->deliveryAgent();
1911 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
1912 const auto &pt = subsceneInfo.eventPointScenePositions.at(pointIndex);
1913 // By tradition, QGuiApplicationPrivate::processTouchEvent() has set the local position to the scene position,
1914 // and Qt Quick expects it to arrive that way: then QQuickDeliveryAgentPrivate::translateTouchEvent()
1915 // copies it into the scene position before localizing.
1916 // That might be silly, we might change it eventually, but gotta stay consistent for now.
1917 QEventPoint &ep = event->point(pointIndex);
1918 QMutableEventPoint::setPosition(ep, pt);
1919 QMutableEventPoint::setScenePosition(ep, pt);
1920 }
1921
1922 if (event->isBeginEvent())
1923 da->setSceneTransform(nullptr);
1924 if (da->event(event)) {
1925 ret = true;
1926 if (QQuickDeliveryAgentPrivate::anyPointGrabbed(event) && !useRayPicking) {
1927 // In case any QEventPoint was grabbed, the relevant QQuickDeliveryAgent needs to know
1928 // how to repeat the picking/coordinate transformation for each update,
1929 // because delivery will bypass internalPick() due to the grab, and it's
1930 // more efficient to avoid whole-scene picking each time anyway.
1931 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(subsceneInfo.obj);
1932 const bool item2Dcase = (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D);
1933 ViewportTransformHelper *transform = new ViewportTransformHelper;
1934 transform->viewport = const_cast<QQuick3DViewport *>(this);
1935 transform->renderer = renderer;
1936 transform->sceneParentNode = static_cast<QSSGRenderNode*>(frontendObjectPrivate->spatialNode);
1937 transform->targetItem = subsceneRoot;
1938 transform->scaleX = window()->effectiveDevicePixelRatio() * m_widthMultiplier;
1939 transform->scaleY = window()->effectiveDevicePixelRatio() * m_heightMultiplier;
1940 transform->uvCoordsArePixels = item2Dcase;
1941 transform->setOnDeliveryAgent(da);
1942 qCDebug(lcPick) << event->type() << "created ViewportTransformHelper on" << da;
1943 }
1944 } else if (event->type() != QEvent::HoverMove) {
1945 qCDebug(lcPick) << subsceneRoot << "didn't want" << event;
1946 }
1947 event->setAccepted(false); // reject implicit grab and let it keep propagating
1948 }
1949 if (visitedSubscenes.isEmpty()) {
1950 event->setAccepted(false);
1951 } else {
1952 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
1953 QMutableEventPoint::setScenePosition(event->point(pointIndex), originalScenePositions.at(pointIndex));
1954 }
1955
1956 // Normally this would occur in QQuickWindowPrivate::clearGrabbers(...) but
1957 // for ray based input, input never goes through QQuickWindow (since events
1958 // are generated from within scene space and not window/screen space).
1959 if (event->isEndEvent() && useRayPicking) {
1960 if (event->isSinglePointEvent()) {
1961 if (static_cast<QSinglePointEvent *>(event)->buttons() == Qt::NoButton) {
1962 auto &firstPt = event->point(0);
1963 event->setExclusiveGrabber(firstPt, nullptr);
1964 event->clearPassiveGrabbers(firstPt);
1965 }
1966 } else {
1967 for (auto &point : event->points()) {
1968 if (point.state() == QEventPoint::State::Released) {
1969 event->setExclusiveGrabber(point, nullptr);
1970 event->clearPassiveGrabbers(point);
1971 }
1972 }
1973 }
1974 }
1975
1976 return ret;
1977}
1978
1979
1980bool QQuick3DViewport::internalPick(QPointerEvent *event, const QVector3D &origin, const QVector3D &direction) const
1981{
1982 QQuick3DSceneRenderer *renderer = getRenderer();
1983 if (!renderer || !event)
1984 return false;
1985
1986 QFlatMap<QQuickItem*, SubsceneInfo> visitedSubscenes;
1987 const bool useRayPicking = !direction.isNull();
1988
1989 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
1990 auto &eventPoint = event->point(pointIndex);
1991 QVarLengthArray<QSSGRenderPickResult, 20> pickResults;
1992 if (Q_UNLIKELY(useRayPicking))
1993 pickResults = getPickResults(renderer, origin, direction);
1994 else
1995 pickResults = getPickResults(renderer, eventPoint);
1996
1997 if (!pickResults.isEmpty())
1998 for (const auto &pickResult : pickResults)
1999 processPickedObject(pickResult, pointIndex, event, visitedSubscenes);
2000 else
2001 eventPoint.setAccepted(false); // let it fall through the viewport to Items underneath
2002 }
2003
2004 return forwardEventToSubscenes(event, useRayPicking, renderer, visitedSubscenes);
2005}
2006
2007bool QQuick3DViewport::singlePointPick(QSinglePointEvent *event, const QVector3D &origin, const QVector3D &direction)
2008{
2009 QQuick3DSceneRenderer *renderer = getRenderer();
2010 if (!renderer || !event)
2011 return false;
2012
2013 QSSGRenderRay ray(origin, direction);
2014
2015 Q_ASSERT(event->pointCount() == 1);
2016 QPointF originalPosition = event->point(0).scenePosition();
2017
2018 auto pickResults = renderer->syncPickAll(ray);
2019
2020 bool delivered = false;
2021
2022 constexpr float jitterLimit = 2.5; // TODO: add property for this?
2023 bool withinJitterLimit = false;
2024
2025 for (const auto &pickResult : pickResults) {
2026 auto [item, position] = getItemAndPosition(pickResult);
2027 if (!item)
2028 break;
2029 if (item == m_prevMouseItem && (position - m_prevMousePos).manhattanLength() < jitterLimit && !event->button()) {
2030 withinJitterLimit = true;
2031 break;
2032 }
2033 auto da = QQuickItemPrivate::get(item)->deliveryAgent();
2034 QEventPoint &ep = event->point(0);
2035 QMutableEventPoint::setPosition(ep, position);
2036 QMutableEventPoint::setScenePosition(ep, position);
2037 if (da->event(event)) {
2038 delivered = true;
2039 if (event->type() == QEvent::MouseButtonPress) {
2040 m_prevMouseItem = item;
2041 m_prevMousePos = position;
2042 withinJitterLimit = true;
2043 }
2044 break;
2045 }
2046 }
2047
2048 QMutableEventPoint::setScenePosition(event->point(0), originalPosition);
2049 if (!withinJitterLimit)
2050 m_prevMouseItem = nullptr;
2051
2052 // Normally this would occur in QQuickWindowPrivate::clearGrabbers(...) but
2053 // for ray based input, input never goes through QQuickWindow (since events
2054 // are generated from within scene space and not window/screen space).
2055 if (event->isEndEvent()) {
2056 if (event->buttons() == Qt::NoButton) {
2057 auto &firstPt = event->point(0);
2058 event->setExclusiveGrabber(firstPt, nullptr);
2059 event->clearPassiveGrabbers(firstPt);
2060 }
2061 }
2062
2063 return delivered;
2064}
2065
2066QPair<QQuickItem *, QPointF> QQuick3DViewport::getItemAndPosition(const QSSGRenderPickResult &pickResult) const
2067{
2068 QQuickItem *subsceneRootItem = nullptr;
2069 QPointF subscenePosition;
2070 const auto backendObject = pickResult.m_hitObject;
2071 const auto frontendObject = findFrontendNode(backendObject);
2072 if (!frontendObject)
2073 return {};
2074 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(frontendObject);
2075 if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
2076 // Item2D, this is the case where there is just an embedded Qt Quick 2D Item
2077 // rendered directly to the scene.
2078 auto item2D = qobject_cast<QQuick3DItem2D *>(frontendObject);
2079 if (item2D)
2080 subsceneRootItem = item2D->contentItem();
2081 if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
2082 return {}; // ignore empty 2D subscenes
2083
2084 // In this case the "UV" coordinates are in pixels in the subscene root item's coordinate system.
2085 subscenePosition = pickResult.m_localUVCoords.toPointF();
2086
2087 // The following code will account for custom input masking, as well any
2088 // transformations that might have been applied to the Item
2089 if (!subsceneRootItem->childAt(subscenePosition.x(), subscenePosition.y()))
2090 return {};
2091 } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
2092 // Model
2093 int materialSubset = pickResult.m_subset;
2094 const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
2095 // Get material
2096 if (backendModel->materials.size() < (pickResult.m_subset + 1))
2097 materialSubset = backendModel->materials.size() - 1;
2098 if (materialSubset < 0)
2099 return {};
2100 const auto backendMaterial = backendModel->materials.at(materialSubset);
2101 const auto frontendMaterial = static_cast<QQuick3DMaterial *>(findFrontendNode(backendMaterial));
2102 subsceneRootItem = getSubSceneRootItem(frontendMaterial);
2103
2104 if (subsceneRootItem) {
2105 // In this case the pick result really is using UV coordinates.
2106 subscenePosition = QPointF(subsceneRootItem->x() + pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
2107 subsceneRootItem->y() - pickResult.m_localUVCoords.y() * subsceneRootItem->height() + subsceneRootItem->height());
2108 }
2109 }
2110 return {subsceneRootItem, subscenePosition};
2111}
2112
2113QVarLengthArray<QSSGRenderPickResult, 20> QQuick3DViewport::getPickResults(QQuick3DSceneRenderer *renderer,
2114 const QVector3D &origin,
2115 const QVector3D &direction) const
2116{
2117 const QSSGRenderRay ray(origin, direction);
2118 return renderer->syncPickAll(ray);
2119}
2120
2121QVarLengthArray<QSSGRenderPickResult, 20> QQuick3DViewport::getPickResults(QQuick3DSceneRenderer *renderer, const QEventPoint &eventPoint) const
2122{
2123 QVarLengthArray<QSSGRenderPickResult, 20> pickResults;
2124 QPointF realPosition = eventPoint.position() * window()->effectiveDevicePixelRatio();
2125 // correct when mapping is not 1:1 due to explicit backing texture size
2126 realPosition.rx() *= m_widthMultiplier;
2127 realPosition.ry() *= m_heightMultiplier;
2128 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(realPosition);
2129 if (rayResult.has_value())
2130 pickResults = renderer->syncPickAll(rayResult.value());
2131 return pickResults;
2132}
2133
2134/*!
2135 \internal
2136
2137 This provides a way to lookup frontendNodes with a backend node taking into consideration both
2138 the scene and the importScene
2139*/
2140QQuick3DObject *QQuick3DViewport::findFrontendNode(const QSSGRenderGraphObject *backendObject) const
2141{
2142 if (!backendObject)
2143 return nullptr;
2144
2145 const auto sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
2146 QQuick3DObject *frontendObject = sceneManager->lookUpNode(backendObject);
2147 if (!frontendObject && m_importScene) {
2148 const auto importSceneManager = QQuick3DObjectPrivate::get(m_importScene)->sceneManager;
2149 frontendObject = importSceneManager->lookUpNode(backendObject);
2150 }
2151 return frontendObject;
2152}
2153
2154QQuick3DPickResult QQuick3DViewport::processPickResult(const QSSGRenderPickResult &pickResult) const
2155{
2156 if (!pickResult.m_hitObject)
2157 return QQuick3DPickResult();
2158
2159 QQuick3DObject *frontendObject = findFrontendNode(pickResult.m_hitObject);
2160
2161 QQuick3DModel *model = qobject_cast<QQuick3DModel *>(frontendObject);
2162 if (model) {
2163 auto itemAndPosition = getItemAndPosition(pickResult);
2164 return QQuick3DPickResult(model,
2165 ::sqrtf(pickResult.m_distanceSq),
2166 pickResult.m_localUVCoords,
2167 pickResult.m_scenePosition,
2168 pickResult.m_localPosition,
2169 pickResult.m_faceNormal,
2170 pickResult.m_sceneNormal,
2171 pickResult.m_instanceIndex,
2172 itemAndPosition.first);
2173 }
2174
2175 QQuick3DItem2D *frontend2DItem = qobject_cast<QQuick3DItem2D *>(frontendObject);
2176 if (frontend2DItem && frontend2DItem->contentItem()) {
2177 // Check if the pick is inside the content item (since the ray just intersected on the items plane)
2178 const QPointF subscenePosition = pickResult.m_localUVCoords.toPointF();
2179 const auto child = frontend2DItem->contentItem()->childAt(subscenePosition.x(), subscenePosition.y());
2180 if (child) {
2181 return QQuick3DPickResult(child,
2182 ::sqrtf(pickResult.m_distanceSq),
2183 QVector2D(frontend2DItem->contentItem()->mapToItem(child, subscenePosition)),
2184 pickResult.m_scenePosition,
2185 pickResult.m_localPosition,
2186 pickResult.m_faceNormal);
2187 }
2188 }
2189
2190 return QQuick3DPickResult();
2191
2192}
2193
2194// Returns the first found scene manager of objects children
2195QQuick3DSceneManager *QQuick3DViewport::findChildSceneManager(QQuick3DObject *inObject, QQuick3DSceneManager *manager)
2196{
2197 if (manager)
2198 return manager;
2199
2200 auto children = QQuick3DObjectPrivate::get(inObject)->childItems;
2201 for (auto child : children) {
2202 if (auto m = QQuick3DObjectPrivate::get(child)->sceneManager) {
2203 manager = m;
2204 break;
2205 }
2206 manager = findChildSceneManager(child, manager);
2207 }
2208 return manager;
2209}
2210
2211void QQuick3DViewport::updateInputProcessing()
2212{
2213 // This should be called from the gui thread.
2214 setAcceptTouchEvents(m_enableInputProcessing);
2215 setAcceptHoverEvents(m_enableInputProcessing);
2216 setAcceptedMouseButtons(m_enableInputProcessing ? Qt::AllButtons : Qt::NoButton);
2217}
2218
2219void QQuick3DViewport::onReleaseCachedResources()
2220{
2221 if (auto renderer = getRenderer())
2222 renderer->releaseCachedResources();
2223}
2224
2225/*!
2226 \qmlproperty List<QtQuick3D::Object3D> View3D::extensions
2227
2228 This property contains a list of user extensions that should be used with this \l View3D.
2229
2230 \sa RenderExtension
2231*/
2232QQmlListProperty<QQuick3DObject> QQuick3DViewport::extensions()
2233{
2234 return QQmlListProperty<QQuick3DObject>{ this,
2235 &m_extensionListDirty,
2236 &QQuick3DExtensionListHelper::extensionAppend,
2237 &QQuick3DExtensionListHelper::extensionCount,
2238 &QQuick3DExtensionListHelper::extensionAt,
2239 &QQuick3DExtensionListHelper::extensionClear,
2240 &QQuick3DExtensionListHelper::extensionReplace,
2241 &QQuick3DExtensionListHelper::extensionRemoveLast};
2242}
2243
2244/*!
2245 \internal
2246 */
2247void QQuick3DViewport::rebuildExtensionList()
2248{
2249 m_extensionListDirty = true;
2250 update();
2251}
2252
2253/*!
2254 \internal
2255
2256 Private constructor for the QQuick3DViewport class so we can differentiate between
2257 a regular QQuick3DViewport and one created for a specific usage, like XR.
2258 */
2259QQuick3DViewport::QQuick3DViewport(PrivateInstanceType type, QQuickItem *parent)
2260 : QQuick3DViewport(parent)
2261{
2262 m_isXrViewInstance = type == PrivateInstanceType::XrViewInstance;
2263}
2264
2265void QQuick3DViewport::updateCameraForLayer(const QQuick3DViewport &view3D, QSSGRenderLayer &layerNode)
2266{
2267 layerNode.explicitCameras.clear();
2268 if (!view3D.m_multiViewCameras.isEmpty()) {
2269 for (QQuick3DCamera *camera : std::as_const(view3D.m_multiViewCameras))
2270 layerNode.explicitCameras.append(static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(camera)->spatialNode));
2271 } else if (view3D.camera()) {
2272 if (QSSGRenderCamera *camera = static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(view3D.camera())->spatialNode))
2273 layerNode.explicitCameras.append(camera);
2274 }
2275
2276 // Ensure these have a parent. All nodes need to be in the node tree somewhere, even if they're technically "parentless"
2277 // or we'll not assign a storage slot for them or update them.
2278 for (QSSGRenderCamera *camera : std::as_const(layerNode.explicitCameras)) {
2279 if (!camera->parent)
2280 layerNode.addChild(*camera);
2281 }
2282}
2283
2284void QQuick3DViewport::updateSceneManagerForImportScene()
2285{
2286 auto privateObject = QQuick3DObjectPrivate::get(m_importScene);
2287 if (!privateObject->sceneManager) {
2288 // If object doesn't already have scene manager, check from its children
2289 QQuick3DSceneManager *manager = findChildSceneManager(m_importScene);
2290 // If still not found, use the one from the scene root (scenes defined outside of an view3d)
2291 if (!manager)
2292 manager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
2293 if (manager) {
2294 manager->setWindow(window());
2295 privateObject->refSceneManager(*manager);
2296 }
2297
2298 // In the case m_sceneRoot doesn't have a valid sceneManager,
2299 // it means the view3d is not valid now.
2300 if (!privateObject->sceneManager)
2301 return;
2302
2303 }
2304 connect(privateObject->sceneManager, &QQuick3DSceneManager::needsUpdate,
2305 this, &QQuickItem::update, Qt::UniqueConnection);
2306 connect(privateObject->sceneManager, &QObject::destroyed,
2307 this, [&](QObject *) {
2308 auto privateObject = QQuick3DObjectPrivate::get(m_importScene);
2309 privateObject->sceneManager = nullptr;
2310 updateSceneManagerForImportScene();
2311 }, Qt::DirectConnection);
2312
2313 QQuick3DNode *scene = m_importScene;
2314 while (scene) {
2315 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
2316 scene = rn ? rn->view3D()->importScene() : nullptr;
2317
2318 if (scene) {
2319 connect(QQuick3DObjectPrivate::get(scene)->sceneManager,
2320 &QQuick3DSceneManager::needsUpdate,
2321 this, &QQuickItem::update, Qt::UniqueConnection);
2322 }
2323 }
2324}
2325
2326QQuick3DViewport::RenderOverrides QQuick3DViewport::renderOverrides() const
2327{
2328 return m_renderOverrides;
2329}
2330
2331void QQuick3DViewport::setRenderOverrides(RenderOverrides newRenderOverrides)
2332{
2333 if (m_renderOverrides == newRenderOverrides)
2334 return;
2335 m_renderOverrides = newRenderOverrides;
2336 emit renderOverridesChanged();
2337
2338 update();
2339}
2340
2341QT_END_NAMESPACE
void run() override
Implement this pure virtual function in your subclass.
CleanupJob(QQuick3DSGDirectRenderer *renderer)
static void extensionClear(QQmlListProperty< QQuick3DObject > *list)
static void extensionReplace(QQmlListProperty< QQuick3DObject > *list, qsizetype idx, QQuick3DObject *o)
static void extensionAppend(QQmlListProperty< QQuick3DObject > *list, QQuick3DObject *extension)
static QQuick3DObject * extensionAt(QQmlListProperty< QQuick3DObject > *list, qsizetype index)
static void extensionRemoveLast(QQmlListProperty< QQuick3DObject > *list)
static qsizetype extensionCount(QQmlListProperty< QQuick3DObject > *list)
Combined button and popup list for selecting options.
static qsizetype ssgn_count(QQmlListProperty< QObject > *property)
static QObject * ssgn_at(QQmlListProperty< QObject > *property, qsizetype i)
static void ssgn_append(QQmlListProperty< QObject > *property, QObject *obj)
static void ssgn_clear(QQmlListProperty< QObject > *property)
QPointF map(const QPointF &viewportPoint) override
QQuick3DSceneRenderer * renderer
void setOnDeliveryAgent(QQuickDeliveryAgent *da)
static QList< QPointer< QQuickDeliveryAgent > > owners
QPointer< QQuick3DViewport > viewport
QPointer< QQuickItem > targetItem
QSSGRenderNode * sceneParentNode