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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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