1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dviewport_p.h"
5#include "qquick3dsceneenvironment_p.h"
6#include "qquick3dscenemanager_p.h"
7#include "qquick3dtexture_p.h"
8#include "qquick3dscenerenderer_p.h"
9#include "qquick3dscenerootnode_p.h"
10#include "qquick3dcamera_p.h"
11#include "qquick3dmodel_p.h"
12#include "qquick3drenderstats_p.h"
13#include "qquick3ditem2d_p.h"
14#include "qquick3ddefaultmaterial_p.h"
15#include "qquick3dprincipledmaterial_p.h"
16#include "qquick3dcustommaterial_p.h"
17#include "qquick3dspecularglossymaterial_p.h"
18
19#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
20#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
21
22#include <QtQuick3DUtils/private/qssgassert_p.h>
23
24#include <qsgtextureprovider.h>
25#include <QSGSimpleTextureNode>
26#include <QSGRendererInterface>
27#include <QQuickWindow>
28#include <QtQuick/private/qquickitem_p.h>
29#include <QtQuick/private/qquickpointerhandler_p.h>
30
31#include <QtQml>
32
33#include <QtGui/private/qeventpoint_p.h>
34
35#include <QtCore/private/qnumeric_p.h>
36
37#include <optional>
38
39QT_BEGIN_NAMESPACE
40
41Q_LOGGING_CATEGORY(lcEv, "qt.quick3d.event")
42Q_LOGGING_CATEGORY(lcPick, "qt.quick3d.pick")
43Q_LOGGING_CATEGORY(lcHover, "qt.quick3d.hover")
44
45static bool isforceInputHandlingSet()
46{
47 static const bool v = (qEnvironmentVariableIntValue(varName: "QT_QUICK3D_FORCE_INPUT_HANDLING") > 0);
48 return v;
49}
50
51struct ViewportTransformHelper : public QQuickDeliveryAgent::Transform
52{
53 static void removeAll() {
54 for (auto o : owners) {
55 if (!o.isNull())
56 o->setSceneTransform(nullptr);
57 }
58 owners.clear();
59 }
60
61 void setOnDeliveryAgent(QQuickDeliveryAgent *da) {
62 da->setSceneTransform(this);
63 owners.append(t: da);
64 }
65
66 /*
67 Transforms viewport coordinates to 2D scene coordinates.
68 Returns the point in targetItem corresponding to \a viewportPoint,
69 assuming that targetItem is mapped onto sceneParentNode.
70 If it's no longer a "hit" on sceneParentNode, returns the last-good point.
71 */
72 QPointF map(const QPointF &viewportPoint) override {
73 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: viewportPoint * dpr);
74 if (rayResult.has_value()) {
75 auto pickResult = renderer->syncPickOne(ray: rayResult.value(), node: sceneParentNode);
76 auto ret = pickResult.m_localUVCoords.toPointF();
77 if (!uvCoordsArePixels) {
78 ret = QPointF(targetItem->x() + ret.x() * targetItem->width(),
79 targetItem->y() - ret.y() * targetItem->height() + targetItem->height());
80 }
81 const bool outOfModel = pickResult.m_localUVCoords.isNull();
82 qCDebug(lcEv) << viewportPoint << "->" << (outOfModel ? "OOM" : "") << ret << "@" << pickResult.m_scenePosition
83 << "UV" << pickResult.m_localUVCoords << "dist" << qSqrt(v: pickResult.m_distanceSq);
84 if (outOfModel) {
85 return lastGoodMapping;
86 } else {
87 lastGoodMapping = ret;
88 return ret;
89 }
90 }
91 return QPointF();
92 }
93
94 QQuick3DSceneRenderer *renderer = nullptr;
95 QSSGRenderNode *sceneParentNode = nullptr;
96 QQuickItem* targetItem = nullptr;
97 qreal dpr = 1;
98 bool uvCoordsArePixels = false; // if false, they are in the range 0..1
99 QPointF lastGoodMapping;
100
101 static QList<QPointer<QQuickDeliveryAgent>> owners;
102};
103
104QList<QPointer<QQuickDeliveryAgent>> ViewportTransformHelper::owners;
105
106class QQuick3DExtensionListHelper
107{
108 Q_DISABLE_COPY_MOVE(QQuick3DExtensionListHelper);
109public:
110 static void extensionAppend(QQmlListProperty<QQuick3DObject> *list, QQuick3DObject *extension)
111 {
112 QSSG_ASSERT(list && extension, return);
113
114 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(object: list->object)) {
115 if (const auto idx = that->m_extensions.indexOf(t: extension); idx == -1) {
116 if (!extension->parentItem())
117 extension->setParentItem(that->m_sceneRoot);
118 that->m_extensions.push_back(t: extension);
119 that->m_extensionListDirty = true;
120 }
121 }
122 }
123 static QQuick3DObject *extensionAt(QQmlListProperty<QQuick3DObject> *list, qsizetype index)
124 {
125 QQuick3DObject *ret = nullptr;
126 QSSG_ASSERT(list, return ret);
127
128 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(object: list->object)) {
129 if (that->m_extensions.size() > index)
130 ret = that->m_extensions.at(i: index);
131 }
132
133 return ret;
134 }
135 static qsizetype extensionCount(QQmlListProperty<QQuick3DObject> *list)
136 {
137 qsizetype ret = -1;
138 QSSG_ASSERT(list, return ret);
139
140 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(object: list->object))
141 ret = that->m_extensions.size();
142
143 return ret;
144 }
145 static void extensionClear(QQmlListProperty<QQuick3DObject> *list)
146 {
147 QSSG_ASSERT(list, return);
148
149 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(object: list->object)) {
150 that->m_extensions.clear();
151 that->m_extensionListDirty = true;
152 }
153 }
154 static void extensionReplace(QQmlListProperty<QQuick3DObject> *list, qsizetype idx, QQuick3DObject *o)
155 {
156 QSSG_ASSERT(list, return);
157
158 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(object: list->object)) {
159 if (that->m_extensions.size() > idx && idx > -1) {
160 that->m_extensions.replace(i: idx, t: o);
161 that->m_extensionListDirty = true;
162 }
163 }
164 }
165 static void extensionRemoveLast(QQmlListProperty<QQuick3DObject> *list)
166 {
167 QSSG_ASSERT(list, return);
168
169 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(object: list->object)) {
170 that->m_extensions.removeLast();
171 that->m_extensionListDirty = true;
172 }
173 }
174};
175
176/*!
177 \qmltype View3D
178 \inherits QQuickItem
179 \inqmlmodule QtQuick3D
180 \brief Provides a viewport on which to render a 3D scene.
181
182 View3D provides a 2D surface on which a 3D scene can be rendered. This
183 surface is a Qt Quick \l Item and can be placed in a Qt Quick scene.
184
185 There are two ways to define the 3D scene that is visualized on the View3D:
186 If you define a hierarchy of \l{Node}{Node-based} items as children of
187 the View3D directly, then this will become the implicit scene of the View3D.
188
189 It is also possible to reference an existing scene by using the \l importScene
190 property and setting it to the root \l Node of the scene you want to visualize.
191 This \l Node does not have to be an ancestor of the View3D, and you can have
192 multiple View3Ds that import the same scene.
193
194 This is demonstrated in \l {Qt Quick 3D - View3D example}{View3D example}.
195
196 If the View3D both has child \l{Node}{Nodes} and the \l importScene property is
197 set simultaneously, then both scenes will be rendered as if they were sibling
198 subtrees in the same scene.
199
200 To control how a scene is rendered, you can set the \l environment
201 property. The type \l SceneEnvironment has a number of visual properties
202 that can be adjusted, such as background color, tone mapping, anti-aliasing
203 and more. \l ExtendedSceneEnvironment in the \c{QtQuick3D.Helpers} module
204 extends \l SceneEnvironment with even more features, adding common
205 post-processing effects.
206
207 In addition, in order for anything to be rendered in the View3D, the scene
208 needs a \l Camera. If there is only a single \l Camera in the scene, then
209 this will automatically be picked. Otherwise, the \l camera property can
210 be used to select the camera. The \l Camera decides which parts of the scene
211 are visible, and how they are projected onto the 2D surface.
212
213 By default, the 3D scene will first be rendered into an off-screen buffer and
214 then composited with the rest of the Qt Quick scene when it is done. This provides
215 the maximum level of compatibility, but may have performance implications on some
216 graphics hardware. If this is the case, the \l renderMode property can be used to
217 switch how the View3D is rendered into the window.
218
219 A View3D with the default Offscreen \l renderMode is implicitly a
220 \l{QSGTextureProvider}{texture provider} as well. This means that \l
221 ShaderEffect or \l{QtQuick3D::Texture::sourceItem}{Texture.sourceItem} can reference
222 the View3D directly as long as all items live within the same
223 \l{QQuickWindow}{window}. Like with any other \l Item, it is also possible
224 to switch the View3D, or one of its ancestors, into a texture-based
225 \l{QtQuick::Item::layer.enabled}{item layer}.
226
227 \sa {Qt Quick 3D - View3D example}
228*/
229
230QQuick3DViewport::QQuick3DViewport(QQuickItem *parent)
231 : QQuickItem(parent)
232{
233 setFlag(flag: ItemHasContents);
234 m_camera = nullptr;
235 m_sceneRoot = new QQuick3DSceneRootNode(this);
236 m_environment = new QQuick3DSceneEnvironment(m_sceneRoot);
237 m_renderStats = new QQuick3DRenderStats();
238 QQuick3DSceneManager *sceneManager = new QQuick3DSceneManager();
239 QQuick3DObjectPrivate::get(item: m_sceneRoot)->refSceneManager(*sceneManager);
240 Q_ASSERT(sceneManager == QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager);
241 connect(sender: sceneManager, signal: &QQuick3DSceneManager::needsUpdate,
242 context: this, slot: &QQuickItem::update);
243
244 // Overrides the internal input handling to always be true
245 // instead of potentially updated after a sync (see updatePaintNode)
246 if (isforceInputHandlingSet()) {
247 m_enableInputProcessing = true;
248 updateInputProcessing();
249 forceActiveFocus();
250 }
251}
252
253QQuick3DViewport::~QQuick3DViewport()
254{
255 // If the quick window still exists, make sure to disconnect any of the direct
256 // connections to this View3D
257 if (auto qw = window())
258 disconnect(sender: qw, signal: nullptr, receiver: this, member: nullptr);
259
260 auto sceneManager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager;
261 if (sceneManager) {
262 sceneManager->setParent(nullptr);
263 if (auto wa = sceneManager->wattached)
264 wa->queueForCleanup(manager: sceneManager);
265 }
266
267 delete m_sceneRoot;
268 m_sceneRoot = nullptr;
269
270 // m_renderStats is tightly coupled with the render thread, so can't delete while we
271 // might still be rendering.
272 m_renderStats->deleteLater();
273
274 // m_directRenderer must be destroyed on the render thread at the proper time, not here.
275 // That's handled in releaseResources() + upon sceneGraphInvalidated
276}
277
278static void ssgn_append(QQmlListProperty<QObject> *property, QObject *obj)
279{
280 if (!obj)
281 return;
282 QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
283
284 if (QQuick3DObject *sceneObject = qmlobject_cast<QQuick3DObject *>(object: obj)) {
285 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(item: view3d->scene())->data();
286 itemProperty.append(&itemProperty, sceneObject);
287 } else {
288 QQuickItemPrivate::data_append(property, obj);
289 }
290}
291
292static qsizetype ssgn_count(QQmlListProperty<QObject> *property)
293{
294 QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
295 if (!view3d || !view3d->scene() || !QQuick3DObjectPrivate::get(item: view3d->scene())->data().count)
296 return 0;
297 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(item: view3d->scene())->data();
298 return itemProperty.count(&itemProperty);
299}
300
301static QObject *ssgn_at(QQmlListProperty<QObject> *property, qsizetype i)
302{
303 QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
304 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(item: view3d->scene())->data();
305 return itemProperty.at(&itemProperty, i);
306}
307
308static void ssgn_clear(QQmlListProperty<QObject> *property)
309{
310 QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
311 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(item: view3d->scene())->data();
312 return itemProperty.clear(&itemProperty);
313}
314
315
316QQmlListProperty<QObject> QQuick3DViewport::data()
317{
318 return QQmlListProperty<QObject>(this,
319 nullptr,
320 ssgn_append,
321 ssgn_count,
322 ssgn_at,
323 ssgn_clear);
324}
325
326/*!
327 \qmlproperty QtQuick3D::Camera QtQuick3D::View3D::camera
328
329 This property specifies which \l Camera is used to render the scene. If this
330 property is not set, then the first enabled camera in the scene will be used.
331
332 \note If this property contains a camera that's not \l {Node::visible}{visible} then
333 no further attempts to find a camera will be done.
334
335 \sa PerspectiveCamera, OrthographicCamera, FrustumCamera, CustomCamera
336*/
337QQuick3DCamera *QQuick3DViewport::camera() const
338{
339 return m_camera;
340}
341
342/*!
343 \qmlproperty QtQuick3D::SceneEnvironment QtQuick3D::View3D::environment
344
345 This property specifies the SceneEnvironment used to render the scene.
346
347 \sa SceneEnvironment
348*/
349QQuick3DSceneEnvironment *QQuick3DViewport::environment() const
350{
351 return m_environment;
352}
353
354/*!
355 \qmlproperty QtQuick3D::Node QtQuick3D::View3D::scene
356 \readonly
357
358 Holds the root \l Node of the View3D's scene.
359
360 \sa importScene
361*/
362QQuick3DNode *QQuick3DViewport::scene() const
363{
364 return m_sceneRoot;
365}
366
367/*!
368 \qmlproperty QtQuick3D::Node QtQuick3D::View3D::importScene
369
370 This property defines the reference node of the scene to render to the viewport.
371 The node does not have to be a child of the View3D. This referenced node becomes
372 a sibling with child nodes of View3D, if there are any.
373
374 \note This property can only be set once, and subsequent changes will have no
375 effect.
376
377 \sa Node
378*/
379QQuick3DNode *QQuick3DViewport::importScene() const
380{
381 return m_importScene;
382}
383
384/*!
385 \qmlproperty enumeration QtQuick3D::View3D::renderMode
386
387 This property determines how the View3D is combined with the other parts of the
388 Qt Quick scene.
389
390 By default, the scene will be rendered into an off-screen buffer as an intermediate
391 step. This off-screen buffer is then rendered into the window (or render target) like any
392 other Qt Quick \l Item.
393
394 For most users, there will be no need to change the render mode, and this property can
395 safely be ignored. But on some graphics hardware, the use of an off-screen buffer can be
396 a performance bottleneck. If this is the case, it might be worth experimenting with other
397 modes.
398
399 \value View3D.Offscreen The scene is rendered into an off-screen buffer as an intermediate
400 step. This off-screen buffer is then composited with the rest of the Qt Quick scene.
401
402 \value View3D.Underlay The scene is rendered directly to the window before the rest of
403 the Qt Quick scene is rendered. With this mode, the View3D cannot be placed on top of
404 other Qt Quick items.
405
406 \value View3D.Overlay The scene is rendered directly to the window after Qt Quick is
407 rendered. With this mode, the View3D will always be on top of other Qt Quick items.
408
409 \value View3D.Inline The View3D's scene graph is embedded into the main scene graph,
410 and the same ordering semantics are applied as to any other Qt Quick \l Item. As this
411 mode can lead to subtle issues, depending on the contents of the scene, due to
412 injecting depth-based 3D content into a 2D scene graph, it is not recommended to be
413 used, unless a specific need arises.
414
415 The default is \c{View3D.Offscreen}.
416
417 \note When changing the render mode, it is important to note that \c{View3D.Offscreen} (the
418 default) is the only mode which guarantees perfect graphics fidelity. The other modes
419 all have limitations that can cause visual glitches, so it is important to check that
420 the visual output still looks correct when changing this property.
421
422 \note When using the Underlay, Overlay, or Inline modes, it can be useful, and in some
423 cases, required, to disable the Qt Quick scene graph's depth buffer writes via
424 QQuickGraphicsConfiguration::setDepthBufferFor2D() before showing the QQuickWindow or
425 QQuickView hosting the View3D item.
426*/
427QQuick3DViewport::RenderMode QQuick3DViewport::renderMode() const
428{
429 return m_renderMode;
430}
431
432/*!
433 \qmlproperty enumeration QtQuick3D::View3D::renderFormat
434 \since 6.4
435
436 This property determines the backing texture's format. Applicable only when
437 the View3D is rendering to a texture, for example because the \l renderMode
438 is \c{View3D.Offscreen}.
439
440 The default is \c{ShaderEffectSource.RGBA8}.
441
442 If the format is not supported by the underlying graphics driver at run
443 time, RGBA8 is used.
444
445 \list
446 \li ShaderEffectSource.RGBA8
447 \li ShaderEffectSource.RGBA16F
448 \li ShaderEffectSource.RGBA32F
449 \endlist
450
451 \sa QtQuick::ShaderEffectSource::format, QtQuick::Item::layer.format
452 */
453QQuickShaderEffectSource::Format QQuick3DViewport::renderFormat() const
454{
455 return m_renderFormat;
456}
457
458/*!
459 \qmlproperty QtQuick3D::RenderStats QtQuick3D::View3D::renderStats
460 \readonly
461
462 This property provides statistics about the rendering of a frame, such as
463 \l {RenderStats::fps}{fps}, \l {RenderStats::frameTime}{frameTime},
464 \l {RenderStats::renderTime}{renderTime}, \l {RenderStats::syncTime}{syncTime},
465 and \l {RenderStats::maxFrameTime}{maxFrameTime}.
466*/
467QQuick3DRenderStats *QQuick3DViewport::renderStats() const
468{
469 return m_renderStats;
470}
471
472QQuick3DSceneRenderer *QQuick3DViewport::createRenderer() const
473{
474 QQuick3DSceneRenderer *renderer = nullptr;
475
476 if (QQuickWindow *qw = window()) {
477 auto wa = QQuick3DSceneManager::getOrSetWindowAttachment(window&: *qw);
478 auto rci = wa->rci();
479 if (!rci) {
480 QSGRendererInterface *rif = qw->rendererInterface();
481 if (QSSG_GUARD(QSGRendererInterface::isApiRhiBased(rif->graphicsApi()))) {
482 QRhi *rhi = static_cast<QRhi *>(rif->getResource(window: qw, resource: QSGRendererInterface::RhiResource));
483 QSSG_CHECK_X(rhi != nullptr, "No QRhi from QQuickWindow, this cannot happen");
484 // The RenderContextInterface, and the objects owned by it (such
485 // as, the BufferManager) are always per-QQuickWindow, and so per
486 // scenegraph render thread. Hence the association with window.
487 // Multiple View3Ds in the same window can use the same rendering
488 // infrastructure (so e.g. the same QSSGBufferManager), but two
489 // View3D objects in different windows must not, except for certain
490 // components that do not work with and own native graphics
491 // resources (most notably, QSSGShaderLibraryManager - but this
492 // distinction is handled internally by QSSGRenderContextInterface).
493 rci = std::make_shared<QSSGRenderContextInterface>(args&: rhi);
494 wa->setRci(rci);
495
496 // Use DirectConnection to stay on the render thread, if there is one.
497 connect(sender: wa, signal: &QQuick3DWindowAttachment::releaseCachedResources, context: this,
498 slot: &QQuick3DViewport::onReleaseCachedResources, type: Qt::DirectConnection);
499
500 } else {
501 qWarning(msg: "The Qt Quick scene is using a rendering method that is not based on QRhi and a 3D graphics API. "
502 "Qt Quick 3D is not functional in such an environment. The View3D item is not going to display anything.");
503 }
504 }
505
506 if (rci)
507 renderer = new QQuick3DSceneRenderer(rci);
508 }
509
510 return renderer;
511}
512
513bool QQuick3DViewport::isTextureProvider() const
514{
515 // We can only be a texture provider if we are rendering to a texture first
516 if (m_renderMode == QQuick3DViewport::Offscreen)
517 return true;
518
519 return false;
520}
521
522QSGTextureProvider *QQuick3DViewport::textureProvider() const
523{
524 // When Item::layer::enabled == true, QQuickItem will be a texture
525 // provider. In this case we should prefer to return the layer rather
526 // than the fbo texture.
527 if (QQuickItem::isTextureProvider())
528 return QQuickItem::textureProvider();
529
530 // We can only be a texture provider if we are rendering to a texture first
531 if (m_renderMode != QQuick3DViewport::Offscreen)
532 return nullptr;
533
534 QQuickWindow *w = window();
535 if (!w) {
536 qWarning(msg: "QSSGView3D::textureProvider: can only be queried on the rendering thread of an exposed window");
537 return nullptr;
538 }
539
540 if (!m_node)
541 m_node = new SGFramebufferObjectNode;
542 return m_node;
543}
544
545class CleanupJob : public QRunnable
546{
547public:
548 CleanupJob(QQuick3DSGDirectRenderer *renderer) : m_renderer(renderer) { }
549 void run() override { delete m_renderer; }
550private:
551 QQuick3DSGDirectRenderer *m_renderer;
552};
553
554void QQuick3DViewport::releaseResources()
555{
556 if (m_directRenderer) {
557 window()->scheduleRenderJob(job: new CleanupJob(m_directRenderer), schedule: QQuickWindow::BeforeSynchronizingStage);
558 m_directRenderer = nullptr;
559 }
560
561 m_node = nullptr;
562}
563
564void QQuick3DViewport::cleanupDirectRenderer()
565{
566 delete m_directRenderer;
567 m_directRenderer = nullptr;
568}
569
570void QQuick3DViewport::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
571{
572 QQuickItem::geometryChange(newGeometry, oldGeometry);
573
574 if (newGeometry.size() != oldGeometry.size())
575 update();
576}
577
578QSGNode *QQuick3DViewport::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *)
579{
580 // When changing render modes
581 if (m_renderModeDirty) {
582 if (node) {
583 delete node;
584 node = nullptr;
585 m_node = nullptr;
586 m_renderNode = nullptr;
587 }
588 if (m_directRenderer) {
589 delete m_directRenderer;
590 m_directRenderer = nullptr;
591 }
592 }
593
594 m_renderModeDirty = false;
595
596 switch (m_renderMode) {
597 // Direct rendering
598 case Underlay:
599 Q_FALLTHROUGH();
600 case Overlay:
601 setupDirectRenderer(m_renderMode);
602 node = nullptr;
603 break;
604 case Offscreen:
605 node = setupOffscreenRenderer(node);
606 break;
607 case Inline:
608 // QSGRenderNode-based rendering
609 node = setupInlineRenderer(node);
610 break;
611 }
612
613 if (!isforceInputHandlingSet()) {
614 // Implicitly enable internal input processing if any item2ds are present.
615 const auto inputHandlingEnabled =
616 QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager->inputHandlingEnabled;
617 const auto enable = inputHandlingEnabled > 0;
618 if (m_enableInputProcessing != enable) {
619 m_enableInputProcessing = enable;
620 QMetaObject::invokeMethod(obj: this, member: "updateInputProcessing", c: Qt::QueuedConnection);
621 }
622 }
623
624 return node;
625}
626
627void QQuick3DViewport::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
628{
629 if (change == ItemSceneChange) {
630 if (value.window) {
631 // TODO: if we want to support multiple windows, there has to be a scene manager for
632 // every window.
633 QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager->setWindow(value.window);
634 if (m_importScene)
635 QQuick3DObjectPrivate::get(item: m_importScene)->sceneManager->setWindow(value.window);
636 m_renderStats->setWindow(value.window);
637 }
638 } else if (change == ItemVisibleHasChanged && isVisible()) {
639 update();
640 }
641}
642
643bool QQuick3DViewport::event(QEvent *event)
644{
645 if (m_enableInputProcessing && event->isPointerEvent())
646 return internalPick(event: static_cast<QPointerEvent *>(event));
647 else
648 return QQuickItem::event(event);
649}
650
651void QQuick3DViewport::componentComplete()
652{
653 QQuickItem::componentComplete();
654 Q_QUICK3D_PROFILE_REGISTER(this);
655}
656
657void QQuick3DViewport::setCamera(QQuick3DCamera *camera)
658{
659 if (m_camera == camera)
660 return;
661
662 if (camera && !camera->parentItem())
663 camera->setParentItem(m_sceneRoot);
664 if (camera)
665 camera->updateGlobalVariables(inViewport: QRect(0, 0, width(), height()));
666
667 QQuick3DObjectPrivate::attachWatcherPriv(sceneContext: m_sceneRoot, callContext: this, setter: &QQuick3DViewport::setCamera, newO: camera, oldO: m_camera);
668
669 m_camera = camera;
670 emit cameraChanged();
671 update();
672}
673
674void QQuick3DViewport::setEnvironment(QQuick3DSceneEnvironment *environment)
675{
676 if (m_environment == environment)
677 return;
678
679 m_environment = environment;
680 if (m_environment && !m_environment->parentItem())
681 m_environment->setParentItem(m_sceneRoot);
682 emit environmentChanged();
683 update();
684}
685
686void QQuick3DViewport::setImportScene(QQuick3DNode *inScene)
687{
688 // ### We may need consider the case where there is
689 // already a scene tree here
690 // FIXME : Only the first importScene is an effective one
691 if (m_importScene)
692 return;
693
694 // FIXME : Check self-import or cross-import
695 // Currently it does not work since importScene qml parsed in a reverse order.
696 QQuick3DNode *scene = inScene;
697 while (scene) {
698 if (m_sceneRoot == scene) {
699 qmlWarning(me: this) << "Cannot allow self-import or cross-import!";
700 return;
701 }
702
703 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(object: scene);
704 scene = rn ? rn->view3D()->importScene() : nullptr;
705 }
706
707 m_importScene = inScene;
708 if (m_importScene) {
709 auto privateObject = QQuick3DObjectPrivate::get(item: m_importScene);
710 if (!privateObject->sceneManager) {
711 // If object doesn't already have scene manager, check from its children
712 QQuick3DSceneManager *manager = findChildSceneManager(inObject: m_importScene);
713 // If still not found, use the one from the scene root (scenes defined outside of an view3d)
714 if (!manager)
715 manager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager;
716 if (manager) {
717 manager->setWindow(window());
718 privateObject->refSceneManager(*manager);
719 }
720 // At this point some manager will exist
721 Q_ASSERT(privateObject->sceneManager);
722 }
723
724 connect(sender: privateObject->sceneManager, signal: &QQuick3DSceneManager::needsUpdate,
725 context: this, slot: &QQuickItem::update);
726
727 QQuick3DNode *scene = inScene;
728 while (scene) {
729 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(object: scene);
730 scene = rn ? rn->view3D()->importScene() : nullptr;
731
732 if (scene) {
733 connect(sender: QQuick3DObjectPrivate::get(item: scene)->sceneManager,
734 signal: &QQuick3DSceneManager::needsUpdate,
735 context: this, slot: &QQuickItem::update);
736 }
737 }
738 }
739
740 emit importSceneChanged();
741 update();
742}
743
744void QQuick3DViewport::setRenderMode(QQuick3DViewport::RenderMode renderMode)
745{
746 if (m_renderMode == renderMode)
747 return;
748
749 m_renderMode = renderMode;
750 m_renderModeDirty = true;
751 emit renderModeChanged();
752 update();
753}
754
755void QQuick3DViewport::setRenderFormat(QQuickShaderEffectSource::Format format)
756{
757 if (m_renderFormat == format)
758 return;
759
760 m_renderFormat = format;
761 m_renderModeDirty = true;
762 emit renderFormatChanged();
763 update();
764}
765
766
767/*!
768 \qmlmethod vector3d View3D::mapFrom3DScene(vector3d scenePos)
769
770 Transforms \a scenePos from scene space (3D) into view space (2D).
771
772 The returned x- and y-values will be be in view coordinates, with the top-left
773 corner at [0, 0] and the bottom-right corner at [width, height]. The returned
774 z-value contains the distance from the near clip plane of the frustum (clipNear) to
775 \a scenePos in scene coordinates. If the distance is negative, that means the \a scenePos
776 is behind the active camera. If \a scenePos cannot be mapped to a position in the scene,
777 a position of [0, 0, 0] is returned.
778
779 This function requires that \l camera is assigned to the view.
780
781 \sa mapTo3DScene(), {Camera::mapToViewport()}{Camera.mapToViewport()}
782*/
783QVector3D QQuick3DViewport::mapFrom3DScene(const QVector3D &scenePos) const
784{
785 if (!m_camera) {
786 qmlWarning(me: this) << "Cannot resolve view position without a camera assigned!";
787 return QVector3D(0, 0, 0);
788 }
789
790 qreal _width = width();
791 qreal _height = height();
792 if (_width == 0 || _height == 0)
793 return QVector3D(0, 0, 0);
794
795 const QVector3D normalizedPos = m_camera->mapToViewport(scenePos, width: _width, height: _height);
796 return normalizedPos * QVector3D(float(_width), float(_height), 1);
797}
798
799/*!
800 \qmlmethod vector3d View3D::mapTo3DScene(vector3d viewPos)
801
802 Transforms \a viewPos from view space (2D) into scene space (3D).
803
804 The x- and y-values of \a viewPos should be in view coordinates, with the top-left
805 corner at [0, 0] and the bottom-right corner at [width, height]. The z-value is
806 interpreted as the distance from the near clip plane of the frustum (clipNear) in
807 scene coordinates.
808
809 If \a viewPos cannot successfully be mapped to a position in the scene, a position of
810 [0, 0, 0] is returned.
811
812 This function requires that a \l camera is assigned to the view.
813
814 \sa mapFrom3DScene(), {Camera::mapFromViewport}{Camera.mapFromViewport()}
815*/
816QVector3D QQuick3DViewport::mapTo3DScene(const QVector3D &viewPos) const
817{
818 if (!m_camera) {
819 qmlWarning(me: this) << "Cannot resolve scene position without a camera assigned!";
820 return QVector3D(0, 0, 0);
821 }
822
823 qreal _width = width();
824 qreal _height = height();
825 if (_width == 0 || _height == 0)
826 return QVector3D(0, 0, 0);
827
828 const QVector3D normalizedPos = viewPos / QVector3D(float(_width), float(_height), 1);
829 return m_camera->mapFromViewport(viewportPos: normalizedPos, width: _width, height: _height);
830}
831
832/*!
833 \qmlmethod PickResult View3D::pick(float x, float y)
834
835 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
836 and return information about the nearest intersection with an object in the scene.
837
838 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
839*/
840QQuick3DPickResult QQuick3DViewport::pick(float x, float y) const
841{
842 QQuick3DSceneRenderer *renderer = getRenderer();
843 if (!renderer)
844 return QQuick3DPickResult();
845
846 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(),
847 qreal(y) * window()->effectiveDevicePixelRatio());
848 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: position);
849 if (!rayResult.has_value())
850 return QQuick3DPickResult();
851
852 return processPickResult(pickResult: renderer->syncPick(ray: rayResult.value()));
853
854}
855
856/*!
857 \qmlmethod List<PickResult> View3D::pickAll(float x, float y)
858
859 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
860 and return a list of information about intersections with objects in the scene.
861 The returned list is sorted by distance from the camera with the nearest
862 intersections appearing first and the furthest appearing last.
863
864 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
865
866 \since 6.2
867*/
868QList<QQuick3DPickResult> QQuick3DViewport::pickAll(float x, float y) const
869{
870 QQuick3DSceneRenderer *renderer = getRenderer();
871 if (!renderer)
872 return QList<QQuick3DPickResult>();
873
874 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(),
875 qreal(y) * window()->effectiveDevicePixelRatio());
876 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: position);
877 if (!rayResult.has_value())
878 return QList<QQuick3DPickResult>();
879
880 const auto resultList = renderer->syncPickAll(ray: rayResult.value());
881 QList<QQuick3DPickResult> processedResultList;
882 processedResultList.reserve(asize: resultList.size());
883 for (const auto &result : resultList)
884 processedResultList.append(t: processPickResult(pickResult: result));
885
886 return processedResultList;
887}
888
889/*!
890 \qmlmethod PickResult View3D::rayPick(vector3d origin, vector3d direction)
891
892 This method will "shoot" a ray into the scene starting at \a origin and in
893 \a direction and return information about the nearest intersection with an
894 object in the scene.
895
896 This can, for instance, be called with the position and forward vector of
897 any object in a scene to see what object is in front of an item. This
898 makes it possible to do picking from any point in the scene.
899
900 \since 6.2
901*/
902QQuick3DPickResult QQuick3DViewport::rayPick(const QVector3D &origin, const QVector3D &direction) const
903{
904 QQuick3DSceneRenderer *renderer = getRenderer();
905 if (!renderer)
906 return QQuick3DPickResult();
907
908 const QSSGRenderRay ray(origin, direction);
909
910 return processPickResult(pickResult: renderer->syncPick(ray));
911}
912
913/*!
914 \qmlmethod List<PickResult> View3D::rayPickAll(vector3d origin, vector3d direction)
915
916 This method will "shoot" a ray into the scene starting at \a origin and in
917 \a direction and return a list of information about the nearest intersections with
918 objects in the scene.
919 The list is presorted by distance from the origin along the direction
920 vector with the nearest intersections appearing first and the furthest
921 appearing last.
922
923 This can, for instance, be called with the position and forward vector of
924 any object in a scene to see what objects are in front of an item. This
925 makes it possible to do picking from any point in the scene.
926
927 \since 6.2
928*/
929QList<QQuick3DPickResult> QQuick3DViewport::rayPickAll(const QVector3D &origin, const QVector3D &direction) const
930{
931 QQuick3DSceneRenderer *renderer = getRenderer();
932 if (!renderer)
933 return QList<QQuick3DPickResult>();
934
935 const QSSGRenderRay ray(origin, direction);
936
937 const auto resultList = renderer->syncPickAll(ray);
938 QList<QQuick3DPickResult> processedResultList;
939 processedResultList.reserve(asize: resultList.size());
940 for (const auto &result : resultList)
941 processedResultList.append(t: processPickResult(pickResult: result));
942
943 return processedResultList;
944}
945
946void QQuick3DViewport::processPointerEventFromRay(const QVector3D &origin, const QVector3D &direction, QPointerEvent *event)
947{
948 internalPick(event, origin, direction);
949}
950
951QQuick3DLightmapBaker *QQuick3DViewport::maybeLightmapBaker()
952{
953 return m_lightmapBaker;
954}
955
956QQuick3DLightmapBaker *QQuick3DViewport::lightmapBaker()
957{
958 if (!m_lightmapBaker)
959 m_lightmapBaker= new QQuick3DLightmapBaker(this);
960
961 return m_lightmapBaker;
962}
963
964/*!
965 \internal
966*/
967void QQuick3DViewport::bakeLightmap()
968{
969 lightmapBaker()->bake();
970}
971
972void QQuick3DViewport::setGlobalPickingEnabled(bool isEnabled)
973{
974 QQuick3DSceneRenderer *renderer = getRenderer();
975 if (!renderer)
976 return;
977
978 renderer->setGlobalPickingEnabled(isEnabled);
979}
980
981void QQuick3DViewport::invalidateSceneGraph()
982{
983 m_node = nullptr;
984}
985
986QQuick3DSceneRenderer *QQuick3DViewport::getRenderer() const
987{
988 QQuick3DSceneRenderer *renderer = nullptr;
989 if (m_node) {
990 renderer = m_node->renderer;
991 } else if (m_renderNode) {
992 renderer = m_renderNode->renderer;
993 } else if (m_directRenderer) {
994 renderer = m_directRenderer->renderer();
995 }
996 return renderer;
997}
998
999void QQuick3DViewport::updateDynamicTextures()
1000{
1001 // Update QSGDynamicTextures that are used for source textures and Quick items
1002 // Must be called on the render thread.
1003
1004 const auto &sceneManager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager;
1005 for (auto *texture : std::as_const(t&: sceneManager->qsgDynamicTextures))
1006 texture->updateTexture();
1007
1008 QQuick3DNode *scene = m_importScene;
1009 while (scene) {
1010 const auto &importSm = QQuick3DObjectPrivate::get(item: scene)->sceneManager;
1011 if (importSm != sceneManager) {
1012 for (auto *texture : std::as_const(t&: importSm->qsgDynamicTextures))
1013 texture->updateTexture();
1014 }
1015
1016 // if importScene has another import
1017 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(object: scene);
1018 scene = rn ? rn->view3D()->importScene() : nullptr;
1019 }
1020}
1021
1022QSGNode *QQuick3DViewport::setupOffscreenRenderer(QSGNode *node)
1023{
1024 SGFramebufferObjectNode *n = static_cast<SGFramebufferObjectNode *>(node);
1025
1026 if (!n) {
1027 if (!m_node)
1028 m_node = new SGFramebufferObjectNode;
1029 n = m_node;
1030 }
1031
1032 if (!n->renderer) {
1033 n->window = window();
1034 n->renderer = createRenderer();
1035 if (!n->renderer)
1036 return nullptr;
1037 n->renderer->fboNode = n;
1038 n->quickFbo = this;
1039 connect(sender: window(), SIGNAL(screenChanged(QScreen*)), receiver: n, SLOT(handleScreenChange()));
1040 }
1041 QSize minFboSize = QQuickItemPrivate::get(item: this)->sceneGraphContext()->minimumFBOSize();
1042 QSize desiredFboSize(qMax<int>(a: minFboSize.width(), b: width()),
1043 qMax<int>(a: minFboSize.height(), b: height()));
1044
1045 n->devicePixelRatio = window()->effectiveDevicePixelRatio();
1046 desiredFboSize *= n->devicePixelRatio;
1047
1048 n->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
1049 n->setRect(x: 0, y: 0, w: width(), h: height());
1050 if (checkIsVisible() && isComponentComplete()) {
1051 n->renderer->synchronize(view3D: this, size: desiredFboSize, dpr: n->devicePixelRatio);
1052 if (n->renderer->m_textureNeedsFlip)
1053 n->setTextureCoordinatesTransform(QSGSimpleTextureNode::MirrorVertically);
1054 updateDynamicTextures();
1055 n->scheduleRender();
1056 }
1057
1058 return n;
1059}
1060
1061QSGNode *QQuick3DViewport::setupInlineRenderer(QSGNode *node)
1062{
1063 QQuick3DSGRenderNode *n = static_cast<QQuick3DSGRenderNode *>(node);
1064 if (!n) {
1065 if (!m_renderNode)
1066 m_renderNode = new QQuick3DSGRenderNode;
1067 n = m_renderNode;
1068 }
1069
1070 if (!n->renderer) {
1071 n->window = window();
1072 n->renderer = createRenderer();
1073 if (!n->renderer)
1074 return nullptr;
1075 }
1076
1077 const QSize targetSize = window()->effectiveDevicePixelRatio() * QSize(width(), height());
1078
1079 // checkIsVisible, not isVisible, because, for example, a
1080 // { visible: false; layer.enabled: true } item still needs
1081 // to function normally.
1082 if (checkIsVisible() && isComponentComplete()) {
1083 n->renderer->synchronize(view3D: this, size: targetSize, dpr: window()->effectiveDevicePixelRatio());
1084 updateDynamicTextures();
1085 n->markDirty(bits: QSGNode::DirtyMaterial);
1086 }
1087
1088 return n;
1089}
1090
1091
1092void QQuick3DViewport::setupDirectRenderer(RenderMode mode)
1093{
1094 auto renderMode = (mode == Underlay) ? QQuick3DSGDirectRenderer::Underlay
1095 : QQuick3DSGDirectRenderer::Overlay;
1096 if (!m_directRenderer) {
1097 QQuick3DSceneRenderer *sceneRenderer = createRenderer();
1098 if (!sceneRenderer)
1099 return;
1100 m_directRenderer = new QQuick3DSGDirectRenderer(sceneRenderer, window(), renderMode);
1101 connect(sender: window(), signal: &QQuickWindow::sceneGraphInvalidated, context: this, slot: &QQuick3DViewport::cleanupDirectRenderer, type: Qt::DirectConnection);
1102 }
1103
1104 const QSizeF targetSize = window()->effectiveDevicePixelRatio() * QSizeF(width(), height());
1105 m_directRenderer->setViewport(QRectF(window()->effectiveDevicePixelRatio() * mapToScene(point: QPointF(0, 0)), targetSize));
1106 m_directRenderer->setVisibility(isVisible());
1107 if (isVisible()) {
1108 m_directRenderer->renderer()->synchronize(view3D: this, size: targetSize.toSize(), dpr: window()->effectiveDevicePixelRatio());
1109 updateDynamicTextures();
1110 m_directRenderer->requestRender();
1111 }
1112}
1113
1114// This is used for offscreen mode since we need to check if
1115// this item is used by an effect but hidden
1116bool QQuick3DViewport::checkIsVisible() const
1117{
1118 auto childPrivate = QQuickItemPrivate::get(item: this);
1119 return (childPrivate->explicitVisible ||
1120 (childPrivate->extra.isAllocated() && childPrivate->extra->effectRefCount));
1121
1122}
1123
1124bool QQuick3DViewport::internalPick(QPointerEvent *event, const QVector3D &origin, const QVector3D &direction) const
1125{
1126 // Some non-thread-safe stuff to do input
1127 // First need to get a handle to the renderer
1128 QQuick3DSceneRenderer *renderer = getRenderer();
1129 if (!renderer || !event)
1130 return false;
1131
1132 const bool isHover = QQuickDeliveryAgentPrivate::isHoverEvent(ev: event);
1133 if (!isHover)
1134 qCDebug(lcEv) << event;
1135 struct SubsceneInfo {
1136 QQuick3DObject* obj = nullptr;
1137 QVarLengthArray<QPointF, 16> eventPointScenePositions;
1138 };
1139 QFlatMap<QQuickItem*, SubsceneInfo> visitedSubscenes;
1140
1141 const bool useRayPicking = !direction.isNull();
1142
1143 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
1144 QQuick3DSceneRenderer::PickResultList pickResults;
1145 auto &eventPoint = event->point(i: pointIndex);
1146 if (useRayPicking) {
1147 const QSSGRenderRay ray(origin, direction);
1148 pickResults = renderer->syncPickAll(ray);
1149 } else {
1150 const QPointF realPosition = eventPoint.position() * window()->effectiveDevicePixelRatio();
1151 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: realPosition);
1152 if (rayResult.has_value())
1153 pickResults = renderer->syncPickAll(ray: rayResult.value());
1154 }
1155 if (!isHover)
1156 qCDebug(lcPick) << pickResults.size() << "pick results for" << event->point(i: pointIndex);
1157 if (pickResults.isEmpty()) {
1158 eventPoint.setAccepted(false); // let it fall through the viewport to Items underneath
1159 continue; // next eventPoint
1160 }
1161
1162 const auto sceneManager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager;
1163
1164 for (const auto &pickResult : pickResults) {
1165 auto backendObject = pickResult.m_hitObject;
1166 if (!backendObject)
1167 continue;
1168
1169 QQuick3DObject *frontendObject = sceneManager->lookUpNode(node: backendObject);
1170 if (!frontendObject && m_importScene) {
1171 const auto importSceneManager = QQuick3DObjectPrivate::get(item: m_importScene)->sceneManager;
1172 frontendObject = importSceneManager->lookUpNode(node: backendObject);
1173 }
1174
1175 if (!frontendObject)
1176 continue;
1177
1178 QQuickItem *subsceneRootItem = nullptr;
1179 QPointF subscenePosition;
1180 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(item: frontendObject);
1181 if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
1182 // Item2D
1183 auto item2D = qobject_cast<QQuick3DItem2D *>(object: frontendObject);
1184 if (item2D)
1185 subsceneRootItem = item2D->contentItem();
1186 if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
1187 continue; // ignore empty 2D subscenes
1188 // In this case the "UV" coordinates are in pixels in the subscene root item, so we can just use them.
1189 subscenePosition = pickResult.m_localUVCoords.toPointF();
1190 // Even though an Item2D is an "infinite plane" for rendering purposes,
1191 // avoid delivering events outside the rectangular area that is occupied by child items,
1192 // so that events can "fall through" to other interactive content in the scene or behind it.
1193 if (!subsceneRootItem->childrenRect().contains(p: subscenePosition))
1194 continue;
1195 } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
1196 // Model
1197 int materialSubset = pickResult.m_subset;
1198 const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
1199 // Get material
1200 if (backendModel->materials.size() < (pickResult.m_subset + 1))
1201 materialSubset = backendModel->materials.size() - 1;
1202 if (materialSubset < 0)
1203 continue;
1204 const auto backendMaterial = backendModel->materials.at(i: materialSubset);
1205 const auto frontendMaterial = sceneManager->lookUpNode(node: backendMaterial);
1206 if (!frontendMaterial)
1207 continue;
1208 const auto frontendMaterialPrivate = QQuick3DObjectPrivate::get(item: frontendMaterial);
1209
1210 if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::DefaultMaterial) {
1211 // Default Material
1212 const auto defaultMaterial = qobject_cast<QQuick3DDefaultMaterial *>(object: frontendMaterial);
1213 if (defaultMaterial) {
1214 // Just check for a diffuseMap for now
1215 if (defaultMaterial->diffuseMap() && defaultMaterial->diffuseMap()->sourceItem())
1216 subsceneRootItem = defaultMaterial->diffuseMap()->sourceItem();
1217 }
1218
1219 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::PrincipledMaterial) {
1220 // Principled Material
1221 const auto principledMaterial = qobject_cast<QQuick3DPrincipledMaterial *>(object: frontendMaterial);
1222 if (principledMaterial) {
1223 // Just check for a baseColorMap for now
1224 if (principledMaterial->baseColorMap() && principledMaterial->baseColorMap()->sourceItem())
1225 subsceneRootItem = principledMaterial->baseColorMap()->sourceItem();
1226 }
1227 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::SpecularGlossyMaterial) {
1228 // SpecularGlossy Material
1229 const auto specularGlossyMaterial = qobject_cast<QQuick3DSpecularGlossyMaterial *>(object: frontendMaterial);
1230 if (specularGlossyMaterial) {
1231 // Just check for a albedoMap for now
1232 if (specularGlossyMaterial->albedoMap() && specularGlossyMaterial->albedoMap()->sourceItem())
1233 subsceneRootItem = specularGlossyMaterial->albedoMap()->sourceItem();
1234 }
1235 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::CustomMaterial) {
1236 // Custom Material
1237 const auto customMaterial = qobject_cast<QQuick3DCustomMaterial *>(object: frontendMaterial);
1238 if (customMaterial) {
1239 // This case is a bit harder because we can not know how the textures will be used
1240 const auto &texturesInputs = customMaterial->m_dynamicTextureMaps;
1241 for (const auto &textureInput : texturesInputs) {
1242 if (auto texture = textureInput->texture()) {
1243 if (texture->sourceItem()) {
1244 subsceneRootItem = texture->sourceItem();
1245 break;
1246 }
1247 }
1248 }
1249 }
1250 }
1251 if (subsceneRootItem) {
1252 // In this case the pick result really is using UV coordinates.
1253 subscenePosition = QPointF(subsceneRootItem->x() +
1254 pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
1255 subsceneRootItem->y() -
1256 pickResult.m_localUVCoords.y() * subsceneRootItem->height() +
1257 subsceneRootItem->height());
1258 }
1259 }
1260
1261 if (subsceneRootItem) {
1262 SubsceneInfo &subscene = visitedSubscenes[subsceneRootItem]; // create if not found
1263 subscene.obj = frontendObject;
1264 if (subscene.eventPointScenePositions.size() != event->pointCount()) {
1265 // ensure capacity, and use an out-of-scene position rather than 0,0 by default
1266 constexpr QPointF inf(-qt_inf(), -qt_inf());
1267 subscene.eventPointScenePositions.resize(sz: event->pointCount(), v: inf);
1268 }
1269 subscene.eventPointScenePositions[pointIndex] = subscenePosition;
1270 }
1271 if (isHover)
1272 qCDebug(lcHover) << "hover pick result:" << frontendObject << "@" << pickResult.m_scenePosition
1273 << "UV" << pickResult.m_localUVCoords << "dist" << qSqrt(v: pickResult.m_distanceSq)
1274 << "scene" << subscenePosition << subsceneRootItem;
1275 else
1276 qCDebug(lcPick) << "pick result:" << frontendObject << "@" << pickResult.m_scenePosition
1277 << "UV" << pickResult.m_localUVCoords << "dist" << qSqrt(v: pickResult.m_distanceSq)
1278 << "scene" << subscenePosition << subsceneRootItem;
1279 } // for pick results from each QEventPoint
1280 } // for each QEventPoint
1281
1282 // Now deliver the entire event (all points) to each relevant subscene.
1283 // Maybe only some points fall inside, but QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem()
1284 // makes reduced-subset touch events that contain only the relevant points, when necessary.
1285 bool ret = false;
1286
1287// ViewportTransformHelper::removeAll();
1288 QVarLengthArray<QPointF, 16> originalScenePositions;
1289 originalScenePositions.resize(sz: event->pointCount());
1290 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
1291 originalScenePositions[pointIndex] = event->point(i: pointIndex).scenePosition();
1292 for (auto subscene : visitedSubscenes) {
1293 QQuickItem *subsceneRoot = subscene.first;
1294 auto &subsceneInfo = subscene.second;
1295 Q_ASSERT(subsceneInfo.eventPointScenePositions.size() == event->pointCount());
1296 auto da = QQuickItemPrivate::get(item: subsceneRoot)->deliveryAgent();
1297 if (!isHover)
1298 qCDebug(lcPick) << "delivering to" << subsceneRoot << "via" << da << event;
1299
1300 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
1301 const auto &pt = subsceneInfo.eventPointScenePositions.at(idx: pointIndex);
1302 // By tradition, QGuiApplicationPrivate::processTouchEvent() has set the local position to the scene position,
1303 // and Qt Quick expects it to arrive that way: then QQuickDeliveryAgentPrivate::translateTouchEvent()
1304 // copies it into the scene position before localizing.
1305 // That might be silly, we might change it eventually, but gotta stay consistent for now.
1306 QEventPoint &ep = event->point(i: pointIndex);
1307 QMutableEventPoint::setPosition(p&: ep, arg: pt);
1308 QMutableEventPoint::setScenePosition(p&: ep, arg: pt);
1309 }
1310
1311 if (event->isBeginEvent())
1312 da->setSceneTransform(nullptr);
1313 if (da->event(ev: event)) {
1314 ret = true;
1315 if (QQuickDeliveryAgentPrivate::anyPointGrabbed(ev: event) && !useRayPicking) {
1316 // In case any QEventPoint was grabbed, the relevant QQuickDeliveryAgent needs to know
1317 // how to repeat the picking/coordinate transformation for each update,
1318 // because delivery will bypass internalPick() due to the grab, and it's
1319 // more efficient to avoid whole-scene picking each time anyway.
1320 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(item: subsceneInfo.obj);
1321 const bool item2Dcase = (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D);
1322 ViewportTransformHelper *transform = new ViewportTransformHelper;
1323 transform->renderer = renderer;
1324 transform->sceneParentNode = static_cast<QSSGRenderNode*>(frontendObjectPrivate->spatialNode);
1325 transform->targetItem = subsceneRoot;
1326 transform->dpr = window()->effectiveDevicePixelRatio();
1327 transform->uvCoordsArePixels = item2Dcase;
1328 transform->setOnDeliveryAgent(da);
1329 qCDebug(lcPick) << event->type() << "created ViewportTransformHelper on" << da;
1330 }
1331 } else if (event->type() != QEvent::HoverMove) {
1332 qCDebug(lcPick) << subsceneRoot << "didn't want" << event;
1333 }
1334 event->setAccepted(false); // reject implicit grab and let it keep propagating
1335 }
1336 if (visitedSubscenes.isEmpty()) {
1337 event->setAccepted(false);
1338 } else {
1339 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
1340 QMutableEventPoint::setScenePosition(p&: event->point(i: pointIndex), arg: originalScenePositions.at(idx: pointIndex));
1341 }
1342 return ret;
1343}
1344
1345QQuick3DPickResult QQuick3DViewport::processPickResult(const QSSGRenderPickResult &pickResult) const
1346{
1347 if (!pickResult.m_hitObject)
1348 return QQuick3DPickResult();
1349
1350 auto backendObject = pickResult.m_hitObject;
1351 const auto sceneManager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager;
1352 QQuick3DObject *frontendObject = sceneManager->lookUpNode(node: backendObject);
1353
1354 // FIXME : for the case of consecutive importScenes
1355 if (!frontendObject && m_importScene) {
1356 const auto importSceneManager = QQuick3DObjectPrivate::get(item: m_importScene)->sceneManager;
1357 frontendObject = importSceneManager->lookUpNode(node: backendObject);
1358 }
1359
1360 QQuick3DModel *model = qobject_cast<QQuick3DModel *>(object: frontendObject);
1361 if (!model)
1362 return QQuick3DPickResult();
1363
1364 return QQuick3DPickResult(model,
1365 ::sqrtf(x: pickResult.m_distanceSq),
1366 pickResult.m_localUVCoords,
1367 pickResult.m_scenePosition,
1368 pickResult.m_localPosition,
1369 pickResult.m_faceNormal,
1370 pickResult.m_instanceIndex);
1371}
1372
1373// Returns the first found scene manager of objects children
1374QQuick3DSceneManager *QQuick3DViewport::findChildSceneManager(QQuick3DObject *inObject, QQuick3DSceneManager *manager)
1375{
1376 if (manager)
1377 return manager;
1378
1379 auto children = QQuick3DObjectPrivate::get(item: inObject)->childItems;
1380 for (auto child : children) {
1381 if (auto m = QQuick3DObjectPrivate::get(item: child)->sceneManager) {
1382 manager = m;
1383 break;
1384 }
1385 manager = findChildSceneManager(inObject: child, manager);
1386 }
1387 return manager;
1388}
1389
1390void QQuick3DViewport::updateInputProcessing()
1391{
1392 // This should be called from the gui thread.
1393 setAcceptTouchEvents(m_enableInputProcessing);
1394 setAcceptHoverEvents(m_enableInputProcessing);
1395 setAcceptedMouseButtons(m_enableInputProcessing ? Qt::AllButtons : Qt::NoButton);
1396}
1397
1398void QQuick3DViewport::onReleaseCachedResources()
1399{
1400 if (auto renderer = getRenderer())
1401 renderer->releaseCachedResources();
1402}
1403
1404/*!
1405 \internal
1406*/
1407QQmlListProperty<QQuick3DObject> QQuick3DViewport::extensions()
1408{
1409 return QQmlListProperty<QQuick3DObject>{ this,
1410 &m_extensionListDirty,
1411 &QQuick3DExtensionListHelper::extensionAppend,
1412 &QQuick3DExtensionListHelper::extensionCount,
1413 &QQuick3DExtensionListHelper::extensionAt,
1414 &QQuick3DExtensionListHelper::extensionClear,
1415 &QQuick3DExtensionListHelper::extensionReplace,
1416 &QQuick3DExtensionListHelper::extensionRemoveLast};
1417}
1418
1419QT_END_NAMESPACE
1420

source code of qtquick3d/src/quick3d/qquick3dviewport.cpp