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

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