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