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 (const auto &o : std::as_const(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 m_visibilityChanged = true;
722 update();
723 }
724}
725
726bool QQuick3DViewport::event(QEvent *event)
727{
728 if (m_enableInputProcessing && event->isPointerEvent())
729 return internalPick(static_cast<QPointerEvent *>(event));
730 else
731 return QQuickItem::event(event);
732}
733
734void QQuick3DViewport::componentComplete()
735{
736 QQuickItem::componentComplete();
737 Q_QUICK3D_PROFILE_REGISTER(this);
738}
739
740void QQuick3DViewport::setCamera(QQuick3DCamera *camera)
741{
742 if (m_camera == camera)
743 return;
744
745 if (camera && !camera->parentItem())
746 camera->setParentItem(m_sceneRoot);
747 if (camera)
748 camera->updateGlobalVariables(QRect(0, 0, width(), height()));
749
750 QQuick3DObjectPrivate::attachWatcherPriv(m_sceneRoot, this, &QQuick3DViewport::setCamera, camera, m_camera);
751
752 m_camera = camera;
753 emit cameraChanged();
754 update();
755}
756
757void QQuick3DViewport::setMultiViewCameras(QQuick3DCamera **firstCamera, int count)
758{
759 m_multiViewCameras.clear();
760 bool sendChangeSignal = false;
761 for (int i = 0; i < count; ++i) {
762 QQuick3DCamera *camera = *(firstCamera + i);
763 if (camera) {
764 if (!camera->parentItem())
765 camera->setParentItem(m_sceneRoot);
766 camera->updateGlobalVariables(QRect(0, 0, width(), height()));
767 }
768 if (i == 0) {
769 if (m_camera != camera) {
770 m_camera = camera;
771 sendChangeSignal = true;
772 }
773 }
774
775 m_multiViewCameras.append(camera);
776
777 // ### do we need attachWatcher stuff? the Xr-provided cameras cannot disappear, although the XrActor (the owner) might
778 }
779
780 if (sendChangeSignal)
781 emit cameraChanged();
782
783 update();
784}
785
786void QQuick3DViewport::setEnvironment(QQuick3DSceneEnvironment *environment)
787{
788 if (m_environment == environment)
789 return;
790
791 m_environment = environment;
792 if (m_environment && !m_environment->parentItem())
793 m_environment->setParentItem(m_sceneRoot);
794
795 QQuick3DObjectPrivate::attachWatcherPriv(m_sceneRoot, this, &QQuick3DViewport::setEnvironment, environment, m_environment);
796
797 emit environmentChanged();
798 update();
799}
800
801void QQuick3DViewport::setImportScene(QQuick3DNode *inScene)
802{
803 // ### We may need consider the case where there is
804 // already a scene tree here
805 // FIXME : Only the first importScene is an effective one
806 if (m_importScene)
807 return;
808
809 // FIXME : Check self-import or cross-import
810 // Currently it does not work since importScene qml parsed in a reverse order.
811 QQuick3DNode *scene = inScene;
812 while (scene) {
813 if (m_sceneRoot == scene) {
814 qmlWarning(this) << "Cannot allow self-import or cross-import!";
815 return;
816 }
817
818 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
819 scene = rn ? rn->view3D()->importScene() : nullptr;
820 }
821
822 m_importScene = inScene;
823 if (m_importScene)
824 updateSceneManagerForImportScene();
825
826 emit importSceneChanged();
827 update();
828}
829
830void QQuick3DViewport::setRenderMode(QQuick3DViewport::RenderMode renderMode)
831{
832 if (m_renderMode == renderMode)
833 return;
834
835 m_renderMode = renderMode;
836 m_renderModeDirty = true;
837 emit renderModeChanged();
838 update();
839}
840
841#if QT_CONFIG(quick_shadereffect)
842void QQuick3DViewport::setRenderFormat(QQuickShaderEffectSource::Format format)
843{
844 if (m_renderFormat == format)
845 return;
846
847 m_renderFormat = format;
848 m_renderModeDirty = true;
849 emit renderFormatChanged();
850 update();
851}
852#endif
853
854/*!
855 \qmlproperty int QtQuick3D::View3D::explicitTextureWidth
856 \since 6.7
857
858 The width, in pixels, of the item's associated texture. Relevant when a
859 fixed texture size is desired that does not depend on the item's size. This
860 size has no effect on the geometry of the item (its size and placement
861 within the scene), which means the texture's content will appear scaled up
862 or down (and possibly stretched) onto the item's area.
863
864 By default the value is \c 0. A value of 0 means that texture's size
865 follows the item's size. (\c{texture size in pixels} = \c{item's logical
866 size} * \c{device pixel ratio}).
867
868 \note This property is relevant only when \l renderMode is set to \c
869 Offscreen. Its value is ignored otherwise.
870
871 \sa explicitTextureHeight, effectiveTextureSize, DebugView
872*/
873int QQuick3DViewport::explicitTextureWidth() const
874{
875 return m_explicitTextureWidth;
876}
877
878void QQuick3DViewport::setExplicitTextureWidth(int width)
879{
880 if (m_explicitTextureWidth == width)
881 return;
882
883 m_explicitTextureWidth = width;
884 emit explicitTextureWidthChanged();
885 update();
886}
887
888/*!
889 \qmlproperty int QtQuick3D::View3D::explicitTextureHeight
890 \since 6.7
891
892 The height, in pixels, of the item's associated texture. Relevant when a
893 fixed texture size is desired that does not depend on the item's size. This
894 size has no effect on the geometry of the item (its size and placement
895 within the scene), which means the texture's content will appear scaled up
896 or down (and possibly stretched) onto the item's area.
897
898 By default the value is \c 0. A value of 0 means that texture's size
899 follows the item's size. (\c{texture size in pixels} = \c{item's logical
900 size} * \c{device pixel ratio}).
901
902 \note This property is relevant only when \l renderMode is set to \c
903 Offscreen. Its value is ignored otherwise.
904
905 \sa explicitTextureWidth, effectiveTextureSize, DebugView
906*/
907int QQuick3DViewport::explicitTextureHeight() const
908{
909 return m_explicitTextureHeight;
910}
911
912void QQuick3DViewport::setExplicitTextureHeight(int height)
913{
914 if (m_explicitTextureHeight == height)
915 return;
916
917 m_explicitTextureHeight = height;
918 emit explicitTextureHeightChanged();
919 update();
920}
921
922/*!
923 \qmlproperty size QtQuick3D::View3D::effectiveTextureSize
924 \since 6.7
925
926 This property exposes the size, in pixels, of the underlying color (and
927 depth/stencil) buffers. It is provided for use on the GUI (main) thread, in
928 QML bindings or JavaScript.
929
930 This is a read-only property.
931
932 \note This property is relevant only when \l renderMode is set to
933 \c Offscreen.
934
935 \sa explicitTextureWidth, explicitTextureHeight, DebugView
936*/
937QSize QQuick3DViewport::effectiveTextureSize() const
938{
939 return m_effectiveTextureSize;
940}
941
942
943/*!
944 \qmlmethod vector3d View3D::mapFrom3DScene(vector3d scenePos)
945
946 Transforms \a scenePos from scene space (3D) into view space (2D).
947
948 The returned x- and y-values will be be in view coordinates, with the top-left
949 corner at [0, 0] and the bottom-right corner at [width, height]. The returned
950 z-value contains the distance from the near clip plane of the frustum (clipNear) to
951 \a scenePos in scene coordinates. If the distance is negative, that means the \a scenePos
952 is behind the active camera. If \a scenePos cannot be mapped to a position in the scene,
953 a position of [0, 0, 0] is returned.
954
955 This function requires that \l camera is assigned to the view.
956
957 \sa mapTo3DScene(), {Camera::mapToViewport()}{Camera.mapToViewport()}
958*/
959QVector3D QQuick3DViewport::mapFrom3DScene(const QVector3D &scenePos) const
960{
961 if (!m_camera) {
962 qmlWarning(this) << "Cannot resolve view position without a camera assigned!";
963 return QVector3D(0, 0, 0);
964 }
965
966 qreal _width = width();
967 qreal _height = height();
968 if (_width == 0 || _height == 0)
969 return QVector3D(0, 0, 0);
970
971 const QVector3D normalizedPos = m_camera->mapToViewport(scenePos, _width, _height);
972 return normalizedPos * QVector3D(float(_width), float(_height), 1);
973}
974
975/*!
976 \qmlmethod vector3d View3D::mapTo3DScene(vector3d viewPos)
977
978 Transforms \a viewPos from view space (2D) into scene space (3D).
979
980 The x- and y-values of \a viewPos should be in view coordinates, with the top-left
981 corner at [0, 0] and the bottom-right corner at [width, height]. The z-value is
982 interpreted as the distance from the near clip plane of the frustum (clipNear) in
983 scene coordinates.
984
985 If \a viewPos cannot successfully be mapped to a position in the scene, a position of
986 [0, 0, 0] is returned.
987
988 This function requires that a \l camera is assigned to the view.
989
990 \sa mapFrom3DScene(), {Camera::mapFromViewport}{Camera.mapFromViewport()}
991*/
992QVector3D QQuick3DViewport::mapTo3DScene(const QVector3D &viewPos) const
993{
994 if (!m_camera) {
995 qmlWarning(this) << "Cannot resolve scene position without a camera assigned!";
996 return QVector3D(0, 0, 0);
997 }
998
999 qreal _width = width();
1000 qreal _height = height();
1001 if (_width == 0 || _height == 0)
1002 return QVector3D(0, 0, 0);
1003
1004 const QVector3D normalizedPos = viewPos / QVector3D(float(_width), float(_height), 1);
1005 return m_camera->mapFromViewport(normalizedPos, _width, _height);
1006}
1007
1008/*!
1009 \qmlmethod pickResult View3D::pick(float x, float y)
1010
1011 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1012 and return information about the nearest intersection with an object in the scene.
1013
1014 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1015
1016 \sa pickAll(), pickSubset(), pickInRect()
1017*/
1018QQuick3DPickResult QQuick3DViewport::pick(float x, float y) const
1019{
1020 QQuick3DSceneRenderer *renderer = getRenderer();
1021 if (!renderer)
1022 return QQuick3DPickResult();
1023
1024 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio() * m_widthMultiplier,
1025 qreal(y) * window()->effectiveDevicePixelRatio() * m_heightMultiplier);
1026 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
1027 if (!rayResult.has_value())
1028 return QQuick3DPickResult();
1029
1030 const auto resultList = renderer->syncPick(rayResult.value());
1031 return getNearestPickResult(resultList);
1032}
1033
1034/*!
1035 \qmlmethod pickResult View3D::pick(float x, float y, Model model)
1036
1037 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1038 and return information about the intersection between the ray and the specified \a model.
1039
1040 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1041
1042 \since 6.8
1043
1044 \sa pickAll(), pickSubset(), pickInRect()
1045*/
1046QQuick3DPickResult QQuick3DViewport::pick(float x, float y, QQuick3DModel *model) const
1047{
1048 QQuick3DSceneRenderer *renderer = getRenderer();
1049 if (!renderer)
1050 return QQuick3DPickResult();
1051
1052 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(),
1053 qreal(y) * window()->effectiveDevicePixelRatio());
1054 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
1055
1056 if (!rayResult.has_value())
1057 return QQuick3DPickResult();
1058
1059 const auto renderNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(model)->spatialNode);
1060 const auto resultList = renderer->syncPickOne(rayResult.value(), renderNode);
1061 return getNearestPickResult(resultList);
1062}
1063
1064/*!
1065 \qmlmethod List<pickResult> View3D::pickSubset(float x, float y, list<Model> models)
1066
1067 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1068 and return information about the intersections with the passed in list of \a models.
1069 This will only check against the list of models passed in.
1070 The returned list is sorted by distance from the camera with the nearest
1071 intersections appearing first and the furthest appearing last.
1072
1073 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1074
1075 Works with both property list<Model> and dynamic JavaScript arrays of models.
1076
1077 \since 6.8
1078
1079 \sa pick(), pickAll(), pickInRect()
1080*/
1081QList<QQuick3DPickResult> QQuick3DViewport::pickSubset(float x, float y, const QJSValue &models) const
1082{
1083 QQuick3DSceneRenderer *renderer = getRenderer();
1084 if (!renderer)
1085 return {};
1086
1087 QVarLengthArray<QSSGRenderNode*> renderNodes;
1088 // Check for regular JavaScript array
1089 if (models.isArray()) {
1090 const auto length = models.property(QStringLiteral("length")).toInt();
1091 if (length == 0)
1092 return {};
1093
1094 for (int i = 0; i < length; ++i) {
1095 const auto isQObject = models.property(i).isQObject();
1096 if (!isQObject) {
1097 qmlWarning(this) << "Type provided for picking is not a QObject. Needs to be of type QQuick3DModel.";
1098 continue;
1099 }
1100 const auto obj = models.property(i).toQObject();
1101 const auto model = qobject_cast<QQuick3DModel *>(obj);
1102 if (!model) {
1103 qmlWarning(this) << "Type " << obj->metaObject()->className() << " is not supported for picking. Needs to be of type QQuick3DModel.";
1104 continue;
1105 }
1106 const auto priv = QQuick3DObjectPrivate::get(model);
1107 if (priv && priv->spatialNode) {
1108 renderNodes.push_back(static_cast<QSSGRenderNode*>(priv->spatialNode));
1109 }
1110 }
1111 } else {
1112 // Check for property list<Model>
1113 const auto subsetVariant = models.toVariant();
1114 if (!subsetVariant.isValid() || !subsetVariant.canConvert<QQmlListReference>())
1115 return {};
1116
1117 const auto list = subsetVariant.value<QQmlListReference>();
1118
1119 // Only support array of models
1120 if (list.listElementType()->className() != QQuick3DModel::staticMetaObject.className()) {
1121 qmlWarning(this) << "Type " << list.listElementType()->className() << " is not supported for picking. Needs to be of type QQuick3DModel.";
1122 return {};
1123 }
1124 for (int i = 0; i < list.count(); ++i) {
1125 auto model = static_cast<QQuick3DModel *>(list.at(i));
1126 if (!model)
1127 continue;
1128 auto priv = QQuick3DObjectPrivate::get(model);
1129 if (priv && priv->spatialNode) {
1130 renderNodes.push_back(static_cast<QSSGRenderNode*>(priv->spatialNode));
1131 }
1132 }
1133 }
1134
1135 if (renderNodes.empty())
1136 return {};
1137
1138 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(),
1139 qreal(y) * window()->effectiveDevicePixelRatio());
1140 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
1141 if (!rayResult.has_value())
1142 return {};
1143
1144 const auto resultList = renderer->syncPickSubset(rayResult.value(), renderNodes);
1145
1146 QList<QQuick3DPickResult> processedResultList;
1147 processedResultList.reserve(resultList.size());
1148 for (const auto &result : resultList)
1149 processedResultList.append(processPickResult(result));
1150
1151 return processedResultList;
1152}
1153
1154/*!
1155 \qmlmethod List<pickResult> View3D::pickAll(float x, float y)
1156
1157 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1158 and return a list of information about intersections with objects in the scene.
1159 The returned list is sorted by distance from the camera with the nearest
1160 intersections appearing first and the furthest appearing last.
1161
1162 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1163
1164 \since 6.2
1165
1166 \sa pick(), pickSubset(), pickInRect()
1167*/
1168QList<QQuick3DPickResult> QQuick3DViewport::pickAll(float x, float y) const
1169{
1170 QQuick3DSceneRenderer *renderer = getRenderer();
1171 if (!renderer)
1172 return QList<QQuick3DPickResult>();
1173
1174 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio() * m_widthMultiplier,
1175 qreal(y) * window()->effectiveDevicePixelRatio() * m_heightMultiplier);
1176 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
1177 if (!rayResult.has_value())
1178 return QList<QQuick3DPickResult>();
1179
1180 const auto resultList = renderer->syncPickAll(rayResult.value());
1181 QList<QQuick3DPickResult> processedResultList;
1182 processedResultList.reserve(resultList.size());
1183 for (const auto &result : resultList)
1184 processedResultList.append(processPickResult(result));
1185
1186 return processedResultList;
1187}
1188
1189/*!
1190 \qmlmethod pickResult View3D::rayPick(vector3d origin, vector3d direction)
1191
1192 This method will "shoot" a ray into the scene starting at \a origin and in
1193 \a direction and return information about the nearest intersection with an
1194 object in the scene.
1195
1196 This can, for instance, be called with the position and forward vector of
1197 any object in a scene to see what object is in front of an item. This
1198 makes it possible to do picking from any point in the scene.
1199
1200 \since 6.2
1201*/
1202QQuick3DPickResult QQuick3DViewport::rayPick(const QVector3D &origin, const QVector3D &direction) const
1203{
1204 QQuick3DSceneRenderer *renderer = getRenderer();
1205 if (!renderer)
1206 return QQuick3DPickResult();
1207
1208 const QSSGRenderRay ray(origin, direction);
1209 const auto resultList = renderer->syncPick(ray);
1210 return getNearestPickResult(resultList);
1211}
1212
1213/*!
1214 \qmlmethod List<pickResult> View3D::rayPickAll(vector3d origin, vector3d direction)
1215
1216 This method will "shoot" a ray into the scene starting at \a origin and in
1217 \a direction and return a list of information about the nearest intersections with
1218 objects in the scene.
1219 The list is presorted by distance from the origin along the direction
1220 vector with the nearest intersections appearing first and the furthest
1221 appearing last.
1222
1223 This can, for instance, be called with the position and forward vector of
1224 any object in a scene to see what objects are in front of an item. This
1225 makes it possible to do picking from any point in the scene.
1226
1227 \since 6.2
1228*/
1229QList<QQuick3DPickResult> QQuick3DViewport::rayPickAll(const QVector3D &origin, const QVector3D &direction) const
1230{
1231 QQuick3DSceneRenderer *renderer = getRenderer();
1232 if (!renderer)
1233 return QList<QQuick3DPickResult>();
1234
1235 const QSSGRenderRay ray(origin, direction);
1236
1237 const auto resultList = renderer->syncPickAll(ray);
1238 QList<QQuick3DPickResult> processedResultList;
1239 processedResultList.reserve(resultList.size());
1240 for (const auto &result : resultList) {
1241 auto processedResult = processPickResult(result);
1242 if (processedResult.hitType() != QQuick3DPickResultEnums::HitType::Null)
1243 processedResultList.append(processedResult);
1244 }
1245
1246 return processedResultList;
1247}
1248
1249/*!
1250 \qmlmethod pickResult View3D::rayPick(vector3d origin, vector3d direction, Model model)
1251
1252 This method will "shoot" a ray into the scene starting at \a origin and in
1253 \a direction and return information about the intersection between the ray and the specified \a model.
1254
1255 \since 6.11
1256*/
1257QQuick3DPickResult QQuick3DViewport::rayPick(const QVector3D &origin, const QVector3D &direction, QQuick3DModel *model) const
1258{
1259 QQuick3DSceneRenderer *renderer = getRenderer();
1260 if (!renderer)
1261 return QQuick3DPickResult();
1262
1263 const QSSGRenderRay ray(origin, direction);
1264
1265 const auto renderNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(model)->spatialNode);
1266 const auto resultList = renderer->syncPickOne(ray, renderNode);
1267 return getNearestPickResult(resultList);
1268}
1269
1270/*!
1271 \qmlmethod pickResult View3D::closestPointPick(vector3d origin, float radius, Model model)
1272
1273 This method will find the point on the surface of \a model that is nearest to \a origin, within a distance of
1274 \a radius. If \a model is \c null, the closest object within \a radius will be found.
1275
1276 If no such object exists, \c null is returned.
1277
1278 \since 6.11
1279*/
1280QQuick3DPickResult QQuick3DViewport::closestPointPick(const QVector3D &origin, float radius, QQuick3DModel *model) const
1281{
1282 QQuick3DSceneRenderer *renderer = getRenderer();
1283
1284 if (!renderer)
1285 return QQuick3DPickResult();
1286 QSSGRenderNode *renderNode = nullptr;
1287 if (model) {
1288 renderNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(model)->spatialNode);
1289 if (Q_UNLIKELY(!renderNode))
1290 return QQuick3DPickResult{};
1291 }
1292
1293 const auto pickResult = renderer->syncPickClosestPoint(origin, radius * radius, renderNode);
1294 if (!pickResult.has_value())
1295 return QQuick3DPickResult{};
1296 return processPickResult(pickResult.value());
1297}
1298
1299/*!
1300 \qmlmethod list<Object3D> View3D::pickInRect(point start, point end)
1301 \since 6.11
1302
1303 This method picks all objects within a rectangular region defined by
1304 \a start and \a end points in view coordinates.
1305
1306 Returns a list of objects that are within the specified rectangle. This
1307 can be used for marquee selection, where the user drags a rectangle to
1308 select multiple objects.
1309
1310 \sa pick(), pickAll(), pickSubset()
1311*/
1312QList<QQuick3DObject *> QQuick3DViewport::pickInRect(const QPointF &start, const QPointF &end) const
1313{
1314 const qreal minX = qMin(start.x(), end.x());
1315 const qreal maxX = qMax(start.x(), end.x());
1316 const qreal minY = qMin(start.y(), end.y());
1317 const qreal maxY = qMax(start.y(), end.y());
1318
1319 const qreal ndc[4] = {
1320 2.0f * minX / width() - 1.0f, // left
1321 2.0f * maxX / width() - 1.0f, // right
1322 // flip Y-coordinates
1323 1.0f - 2.0f * maxY / height(), // bottom
1324 1.0f - 2.0f * minY / height(), // top
1325 };
1326
1327 const float near = 0.0f;
1328 const float far = 1.0f;
1329 enum { L, R, B, T }; // left, right, bottom, top
1330 const QVector4D ndcCorners[8] = {
1331 // Near plane
1332 QVector4D(ndc[L], ndc[B], near, 1.0f), // 0: bottom-left | n0
1333 QVector4D(ndc[R], ndc[B], near, 1.0f), // 1: bottom-right | n1
1334 QVector4D(ndc[L], ndc[T], near, 1.0f), // 3: top-left | n2
1335 QVector4D(ndc[R], ndc[T], near, 1.0f), // 2: top-right | n3
1336 // Far plane
1337 QVector4D(ndc[L], ndc[B], far, 1.0f), // 4: bottom-left | f0
1338 QVector4D(ndc[R], ndc[B], far, 1.0f), // 5: bottom-right | f1
1339 QVector4D(ndc[L], ndc[T], far, 1.0f), // 7: top-left | f2
1340 QVector4D(ndc[R], ndc[T], far, 1.0f), // 6: top-right | f3
1341 };
1342
1343 QMatrix4x4 viewProjection;
1344 if (this->camera()) {
1345 if (auto camera = static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(this->camera())->spatialNode))
1346 camera->calculateViewProjectionMatrix(camera->localTransform, camera->projection, viewProjection);
1347 }
1348 QMatrix4x4 viewProjectionInverted = viewProjection.inverted();
1349
1350 QVector3D worldCorners[8];
1351 for (int i = 0; i < 8; ++i) {
1352 QVector4D worldPoint = viewProjectionInverted * ndcCorners[i];
1353 worldCorners[i] = worldPoint.toVector3D() / worldPoint.w(); // perspective divide
1354 }
1355
1356 // Frustum
1357 //
1358 // f2 ------------f3
1359 // | |
1360 // | |
1361 // | |
1362 // | |
1363 // f0 ------------f1
1364 //
1365 // n2 -------- n3
1366 // | |
1367 // | camera |
1368 // | |
1369 // n0 -------- n1
1370 //
1371 // CCW winding if inside the frustum and looking toward the face
1372 const QSSGFrustum frustum {
1373 QSSGPlane(worldCorners[0], worldCorners[4], worldCorners[6]), // L [n0, f0, f2]
1374 QSSGPlane(worldCorners[1], worldCorners[3], worldCorners[7]), // R [n1, n3, f3]
1375 QSSGPlane(worldCorners[0], worldCorners[1], worldCorners[5]), // B [n0, n1, f1]
1376 QSSGPlane(worldCorners[2], worldCorners[6], worldCorners[7]), // T [n2, f2, f3]
1377 QSSGPlane(worldCorners[0], worldCorners[2], worldCorners[3]), // N [n0, n2, n3]
1378 QSSGPlane(worldCorners[5], worldCorners[7], worldCorners[6]), // F [f1, f3, f2]
1379 };
1380
1381 QList<QQuick3DObject *> ret;
1382 if (QQuick3DSceneRenderer *renderer = getRenderer()) {
1383 auto nodes = renderer->syncPickInFrustum(frustum);
1384 for (const auto *node : std::as_const(nodes)) {
1385 if (QQuick3DObject *m = findFrontendNode(node))
1386 ret.append(m);
1387 }
1388 }
1389
1390 return ret;
1391}
1392
1393void QQuick3DViewport::processPointerEventFromRay(const QVector3D &origin, const QVector3D &direction, QPointerEvent *event) const
1394{
1395 internalPick(event, origin, direction);
1396}
1397
1398// Note: we have enough information to implement Capability::Hover and Capability::ZPosition,
1399// but those properties are not currently available in QTouchEvent/QEventPoint
1400
1401namespace {
1402class SyntheticTouchDevice : public QPointingDevice
1403{
1404public:
1405 SyntheticTouchDevice(QObject *parent = nullptr)
1406 : QPointingDevice(QLatin1StringView("QtQuick3D Touch Synthesizer"),
1407 0,
1408 DeviceType::TouchScreen,
1409 PointerType::Finger,
1410 Capability::Position,
1411 10, 0,
1412 QString(), QPointingDeviceUniqueId(),
1413 parent)
1414 {
1415 }
1416};
1417}
1418
1419/*!
1420 \qmlmethod void View3D::setTouchpoint(Item target, point position, int pointId, bool pressed)
1421
1422 Sends a synthetic touch event to \a target, moving the touch point with ID \a pointId to \a position,
1423 with \a pressed determining if the point is pressed.
1424 Also sends the appropriate touch release event if \a pointId was previously active on a different
1425 item.
1426
1427 \since 6.8
1428 */
1429
1430void QQuick3DViewport::setTouchpoint(QQuickItem *target, const QPointF &position, int pointId, bool pressed)
1431{
1432 if (pointId >= m_touchState.size())
1433 m_touchState.resize(pointId + 1);
1434 auto prevState = m_touchState[pointId];
1435
1436 const bool sameTarget = prevState.target == target;
1437 const bool wasPressed = prevState.isPressed;
1438
1439 const bool isPress = pressed && (!sameTarget || !wasPressed);
1440 const bool isRelease = !pressed && wasPressed && sameTarget;
1441
1442 // Hover if we're not active, and we weren't previously active.
1443 // We assume that we always get a non-active for a target when we release.
1444 // This function sends a release events if the target is changed.
1445 if (!sameTarget && wasPressed)
1446 qWarning("QQuick3DViewport::setTouchpoint missing release event");
1447
1448 if (!pressed && !wasPressed) {
1449 // This would be a hover event: skipping
1450 return;
1451 }
1452
1453 m_touchState[pointId] = { target, position, pressed };
1454
1455 if (!m_syntheticTouchDevice)
1456 m_syntheticTouchDevice = new SyntheticTouchDevice(this);
1457
1458 QPointingDevicePrivate *devPriv = QPointingDevicePrivate::get(m_syntheticTouchDevice);
1459
1460 auto makePoint = [devPriv](int id, QEventPoint::State pointState, QPointF pos, quint64 timestamp) -> QEventPoint {
1461 auto epd = devPriv->pointById(id);
1462 auto &ep = epd->eventPoint;
1463 if (pointState != QEventPoint::State::Stationary)
1464 ep.setAccepted(false);
1465
1466 auto res = QMutableEventPoint::withTimeStamp(timestamp, id, pointState, pos, pos, pos);
1467 QMutableEventPoint::update(res, ep);
1468
1469 if (pointState == QEventPoint::State::Pressed)
1470 QMutableEventPoint::setGlobalPressPosition(res, pos);
1471 else if (ep.state() != QEventPoint::State::Unknown)
1472 QMutableEventPoint::setGlobalPressPosition(res, ep.globalPressPosition());
1473
1474 return res;
1475 };
1476
1477 auto sendTouchEvent = [&](QQuickItem *t, const QPointF &position, int pointId, QEventPoint::State pointState, quint64 timestamp) -> void {
1478 QList<QEventPoint> points;
1479 bool otherPoint = false; // Does the event have another point already?
1480 for (int i = 0; i < m_touchState.size(); ++i) {
1481 const auto &ts = m_touchState[i];
1482 if (ts.target != t)
1483 continue;
1484 if (i == pointId) {
1485 auto newPoint = makePoint(i, pointState, position, timestamp);
1486 points << newPoint;
1487 } else if (ts.isPressed) {
1488 otherPoint = true;
1489 points << makePoint(i, QEventPoint::Stationary, ts.position, timestamp);
1490 }
1491 }
1492
1493 QEvent::Type type;
1494 if (pointState == QEventPoint::Pressed && !otherPoint)
1495 type = QEvent::Type::TouchBegin;
1496 else if (pointState == QEventPoint::Released && !otherPoint)
1497 type = QEvent::Type::TouchEnd;
1498 else
1499 type = QEvent::Type::TouchUpdate;
1500
1501 QTouchEvent ev(type, m_syntheticTouchDevice, {}, points);
1502 ev.setTimestamp(timestamp);
1503
1504 if (t) {
1505 // Actually send event:
1506 auto da = QQuickItemPrivate::get(t)->deliveryAgent();
1507 bool handled = da->event(&ev);
1508 Q_UNUSED(handled);
1509 }
1510
1511 // Duplicate logic from QQuickWindowPrivate::clearGrabbers
1512 if (ev.isEndEvent()) {
1513 for (auto &point : ev.points()) {
1514 if (point.state() == QEventPoint::State::Released) {
1515 ev.setExclusiveGrabber(point, nullptr);
1516 ev.clearPassiveGrabbers(point);
1517 }
1518 }
1519 }
1520 };
1521
1522 auto timestamp = QDateTime::currentMSecsSinceEpoch();
1523
1524 // Send a release event to the previous target
1525 if (prevState.target && !sameTarget)
1526 sendTouchEvent(prevState.target, prevState.position, pointId, QEventPoint::Released, timestamp);
1527
1528 // Now send an event for the new state
1529 QEventPoint::State newState = isPress ? QEventPoint::Pressed : isRelease ? QEventPoint::Released : QEventPoint::Updated;
1530 sendTouchEvent(target, position, pointId, newState, timestamp);
1531}
1532
1533QQuick3DLightmapBaker *QQuick3DViewport::maybeLightmapBaker()
1534{
1535 return m_lightmapBaker;
1536}
1537
1538QQuick3DLightmapBaker *QQuick3DViewport::lightmapBaker()
1539{
1540 if (!m_lightmapBaker)
1541 m_lightmapBaker= new QQuick3DLightmapBaker(this);
1542
1543 return m_lightmapBaker;
1544}
1545
1546/*!
1547 \internal
1548*/
1549void QQuick3DViewport::bakeLightmap()
1550{
1551 QQuick3DSceneRenderer *renderer = getRenderer();
1552 if (!renderer || !renderer->m_layer->renderData)
1553 return;
1554
1555 const bool currentlyBaking = renderer->m_layer->renderData->lightmapBaker != nullptr;
1556
1557 if (!currentlyBaking)
1558 lightmapBaker()->bake();
1559}
1560
1561/*!
1562 \internal
1563*/
1564void QQuick3DViewport::denoiseLightmap()
1565{
1566 QQuick3DSceneRenderer *renderer = getRenderer();
1567 if (!renderer || !renderer->m_layer->renderData)
1568 return;
1569
1570 const bool currentlyBaking = renderer->m_layer->renderData->lightmapBaker != nullptr;
1571
1572 if (!currentlyBaking)
1573 lightmapBaker()->denoise();
1574}
1575
1576
1577void QQuick3DViewport::setGlobalPickingEnabled(bool isEnabled)
1578{
1579 QQuick3DSceneRenderer *renderer = getRenderer();
1580 if (!renderer)
1581 return;
1582
1583 renderer->setGlobalPickingEnabled(isEnabled);
1584}
1585
1586void QQuick3DViewport::invalidateSceneGraph()
1587{
1588 m_node = nullptr;
1589}
1590
1591QQuick3DSceneRenderer *QQuick3DViewport::getRenderer() const
1592{
1593 QQuick3DSceneRenderer *renderer = nullptr;
1594 if (m_node) {
1595 renderer = m_node->renderer;
1596 } else if (m_renderNode) {
1597 renderer = m_renderNode->renderer;
1598 } else if (m_directRenderer) {
1599 renderer = m_directRenderer->renderer();
1600 }
1601 return renderer;
1602}
1603
1604void QQuick3DViewport::updateDynamicTextures()
1605{
1606 // Update QSGDynamicTextures that are used for source textures and Quick items
1607 // Must be called on the render thread.
1608
1609 const auto &sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
1610 for (auto *texture : std::as_const(sceneManager->qsgDynamicTextures))
1611 texture->updateTexture();
1612
1613 QQuick3DNode *scene = m_importScene;
1614 while (scene) {
1615 const auto &importSm = QQuick3DObjectPrivate::get(scene)->sceneManager;
1616 if (importSm != sceneManager) {
1617 for (auto *texture : std::as_const(importSm->qsgDynamicTextures))
1618 texture->updateTexture();
1619 }
1620
1621 // if importScene has another import
1622 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
1623 scene = rn ? rn->view3D()->importScene() : nullptr;
1624 }
1625}
1626
1627QSGNode *QQuick3DViewport::setupOffscreenRenderer(QSGNode *node)
1628{
1629 SGFramebufferObjectNode *n = static_cast<SGFramebufferObjectNode *>(node);
1630
1631 if (!n) {
1632 if (!m_node)
1633 m_node = new SGFramebufferObjectNode;
1634 n = m_node;
1635 }
1636
1637 if (!n->renderer) {
1638 n->window = window();
1639 n->renderer = createRenderer();
1640 if (!n->renderer)
1641 return nullptr;
1642 n->renderer->fboNode = n;
1643 n->quickFbo = this;
1644 connect(window(), SIGNAL(screenChanged(QScreen*)), n, SLOT(handleScreenChange()));
1645 }
1646
1647 const qreal dpr = window()->effectiveDevicePixelRatio();
1648 const QSize minFboSize = QQuickItemPrivate::get(this)->sceneGraphContext()->minimumFBOSize();
1649 QSize desiredFboSize = QSize(m_explicitTextureWidth, m_explicitTextureHeight);
1650 if (desiredFboSize.isEmpty()) {
1651 desiredFboSize = QSize(width(), height()) * dpr;
1652 n->devicePixelRatio = dpr;
1653 // 1:1 mapping between the backing texture and the on-screen quad
1654 m_widthMultiplier = 1.0f;
1655 m_heightMultiplier = 1.0f;
1656 } else {
1657 QSize itemPixelSize = QSize(width(), height()) * dpr;
1658 // not 1:1 maping between the backing texture and the on-screen quad
1659 m_widthMultiplier = desiredFboSize.width() / float(itemPixelSize.width());
1660 m_heightMultiplier = desiredFboSize.height() / float(itemPixelSize.height());
1661 n->devicePixelRatio = 1.0;
1662 }
1663 desiredFboSize.setWidth(qMax(minFboSize.width(), desiredFboSize.width()));
1664 desiredFboSize.setHeight(qMax(minFboSize.height(), desiredFboSize.height()));
1665
1666 if (desiredFboSize != m_effectiveTextureSize) {
1667 m_effectiveTextureSize = desiredFboSize;
1668 emit effectiveTextureSizeChanged();
1669 }
1670
1671 n->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
1672 n->setRect(0, 0, width(), height());
1673 if (checkIsVisible() && isComponentComplete()) {
1674 n->renderer->synchronize(this, desiredFboSize, n->devicePixelRatio);
1675 if (n->renderer->m_textureNeedsFlip)
1676 n->setTextureCoordinatesTransform(QSGSimpleTextureNode::MirrorVertically);
1677 updateDynamicTextures();
1678 n->scheduleRender();
1679 }
1680
1681 return n;
1682}
1683
1684QSGNode *QQuick3DViewport::setupInlineRenderer(QSGNode *node)
1685{
1686 QQuick3DSGRenderNode *n = static_cast<QQuick3DSGRenderNode *>(node);
1687 if (!n) {
1688 if (!m_renderNode)
1689 m_renderNode = new QQuick3DSGRenderNode;
1690 n = m_renderNode;
1691 }
1692
1693 if (!n->renderer) {
1694 n->window = window();
1695 n->renderer = createRenderer();
1696 if (!n->renderer)
1697 return nullptr;
1698 }
1699
1700 if (!m_effectiveTextureSize.isEmpty()) {
1701 m_effectiveTextureSize = QSize();
1702 emit effectiveTextureSizeChanged();
1703 }
1704
1705 const QSize targetSize = window()->effectiveDevicePixelRatio() * QSize(width(), height());
1706
1707 // checkIsVisible, not isVisible, because, for example, a
1708 // { visible: false; layer.enabled: true } item still needs
1709 // to function normally.
1710 if (checkIsVisible() && isComponentComplete()) {
1711 n->renderer->synchronize(this, targetSize, window()->effectiveDevicePixelRatio());
1712 updateDynamicTextures();
1713 n->markDirty(QSGNode::DirtyMaterial);
1714 }
1715
1716 return n;
1717}
1718
1719
1720void QQuick3DViewport::setupDirectRenderer(RenderMode mode)
1721{
1722 auto renderMode = (mode == Underlay) ? QQuick3DSGDirectRenderer::Underlay
1723 : QQuick3DSGDirectRenderer::Overlay;
1724 if (!m_directRenderer) {
1725 QQuick3DSceneRenderer *sceneRenderer = createRenderer();
1726 if (!sceneRenderer)
1727 return;
1728 m_directRenderer = new QQuick3DSGDirectRenderer(sceneRenderer, window(), renderMode);
1729 connect(window(), &QQuickWindow::sceneGraphInvalidated, this, &QQuick3DViewport::cleanupDirectRenderer, Qt::DirectConnection);
1730 }
1731
1732 if (!m_effectiveTextureSize.isEmpty()) {
1733 m_effectiveTextureSize = QSize();
1734 emit effectiveTextureSizeChanged();
1735 }
1736
1737 const QSizeF targetSize = window()->effectiveDevicePixelRatio() * QSizeF(width(), height());
1738 m_directRenderer->setViewport(QRectF(window()->effectiveDevicePixelRatio() * mapToScene(QPointF(0, 0)), targetSize));
1739 m_directRenderer->setVisibility(isVisible());
1740 if (isVisible()) {
1741 m_directRenderer->preSynchronize();
1742 m_directRenderer->renderer()->synchronize(this, targetSize.toSize(), window()->effectiveDevicePixelRatio());
1743 updateDynamicTextures();
1744 m_directRenderer->requestRender();
1745 }
1746}
1747
1748// This is used for offscreen mode since we need to check if
1749// this item is used by an effect but hidden
1750bool QQuick3DViewport::checkIsVisible() const
1751{
1752 auto childPrivate = QQuickItemPrivate::get(this);
1753 return (childPrivate->explicitVisible ||
1754 (childPrivate->extra.isAllocated() && childPrivate->extra->effectRefCount));
1755
1756}
1757
1758/*!
1759 \internal
1760
1761 This method processes a pickResult on an object with the objective of checking
1762 if there are any QQuickItem based subscenes that the QPointerEvent needs to be
1763 forwarded to (3D -> 2D). If such a subscene is found, the event will be mapped
1764 to the correct cordinate system, and added to the visitedSubscenes map for later
1765 event delivery.
1766*/
1767void QQuick3DViewport::processPickedObject(const QSSGRenderPickResult &pickResult,
1768 int pointIndex,
1769 QPointerEvent *event,
1770 QFlatMap<QQuickItem *, SubsceneInfo> &visitedSubscenes) const
1771{
1772 QQuickItem *subsceneRootItem = nullptr;
1773 QPointF subscenePosition;
1774 const auto backendObject = pickResult.m_hitObject;
1775 const auto frontendObject = findFrontendNode(backendObject);
1776 if (!frontendObject)
1777 return;
1778
1779 // Figure out if there are any QQuickItem based scenes we need to forward
1780 // the event to, and if the are found, determine how to translate the UV Coords
1781 // in the pickResult based on what type the object containing the scene is.
1782 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(frontendObject);
1783 if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
1784 // Item2D, this is the case where there is just an embedded Qt Quick 2D Item
1785 // rendered directly to the scene.
1786 auto item2D = qobject_cast<QQuick3DItem2D *>(frontendObject);
1787 if (item2D)
1788 subsceneRootItem = item2D->contentItem();
1789 if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
1790 return; // ignore empty 2D subscenes
1791
1792 // In this case the "UV" coordinates are in pixels in the subscene root item's coordinate system.
1793 subscenePosition = pickResult.m_localUVCoords.toPointF();
1794
1795 // The following code will account for custom input masking, as well any
1796 // transformations that might have been applied to the Item
1797 if (!subsceneRootItem->childAt(subscenePosition.x(), subscenePosition.y()))
1798 return;
1799 } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
1800 // Model
1801 int materialSubset = pickResult.m_subset;
1802 const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
1803 // Get material
1804 if (backendModel->materials.size() < (pickResult.m_subset + 1))
1805 materialSubset = backendModel->materials.size() - 1;
1806 if (materialSubset < 0)
1807 return;
1808 const auto backendMaterial = backendModel->materials.at(materialSubset);
1809 const auto frontendMaterial = static_cast<QQuick3DMaterial*>(findFrontendNode(backendMaterial));
1810 subsceneRootItem = getSubSceneRootItem(frontendMaterial);
1811
1812 if (subsceneRootItem) {
1813 // In this case the pick result really is using UV coordinates.
1814 subscenePosition = QPointF(subsceneRootItem->x() + pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
1815 subsceneRootItem->y() - pickResult.m_localUVCoords.y() * subsceneRootItem->height() + subsceneRootItem->height());
1816 }
1817 }
1818
1819 // Add the new event (item and position) to the visitedSubscene map.
1820 if (subsceneRootItem) {
1821 SubsceneInfo &subscene = visitedSubscenes[subsceneRootItem]; // create if not found
1822 subscene.obj = frontendObject;
1823 if (subscene.eventPointScenePositions.size() != event->pointCount()) {
1824 // ensure capacity, and use an out-of-scene position rather than 0,0 by default
1825 constexpr QPointF inf(-qt_inf(), -qt_inf());
1826 subscene.eventPointScenePositions.resize(event->pointCount(), inf);
1827 }
1828 subscene.eventPointScenePositions[pointIndex] = subscenePosition;
1829 }
1830}
1831
1832/*!
1833 \internal
1834
1835 This method will try and find a QQuickItem based by looking for sourceItem()
1836 properties on the primary color channels for the various material types.
1837
1838 \note for CustomMaterial there is just a best effort given where the first
1839 associated Texture with a sourceItem property is used.
1840*/
1841
1842QQuickItem *QQuick3DViewport::getSubSceneRootItem(QQuick3DMaterial *material) const
1843{
1844 if (!material)
1845 return nullptr;
1846
1847 QQuickItem *subsceneRootItem = nullptr;
1848 const auto frontendMaterialPrivate = QQuick3DObjectPrivate::get(material);
1849
1850 if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::DefaultMaterial) {
1851 // Default Material
1852 const auto defaultMaterial = qobject_cast<QQuick3DDefaultMaterial *>(material);
1853 if (defaultMaterial) {
1854 // Just check for a diffuseMap for now
1855 if (defaultMaterial->diffuseMap() && defaultMaterial->diffuseMap()->sourceItem())
1856 subsceneRootItem = defaultMaterial->diffuseMap()->sourceItem();
1857 }
1858
1859 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::PrincipledMaterial) {
1860 // Principled Material
1861 const auto principledMaterial = qobject_cast<QQuick3DPrincipledMaterial *>(material);
1862 if (principledMaterial) {
1863 // Just check for a baseColorMap for now
1864 if (principledMaterial->baseColorMap() && principledMaterial->baseColorMap()->sourceItem())
1865 subsceneRootItem = principledMaterial->baseColorMap()->sourceItem();
1866 }
1867 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::SpecularGlossyMaterial) {
1868 // SpecularGlossy Material
1869 const auto specularGlossyMaterial = qobject_cast<QQuick3DSpecularGlossyMaterial *>(material);
1870 if (specularGlossyMaterial) {
1871 // Just check for a albedoMap for now
1872 if (specularGlossyMaterial->albedoMap() && specularGlossyMaterial->albedoMap()->sourceItem())
1873 subsceneRootItem = specularGlossyMaterial->albedoMap()->sourceItem();
1874 }
1875 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::CustomMaterial) {
1876 // Custom Material
1877 const auto customMaterial = qobject_cast<QQuick3DCustomMaterial *>(material);
1878 if (customMaterial) {
1879 // This case is a bit harder because we can not know how the textures will be used
1880 const auto &texturesInputs = customMaterial->m_dynamicTextureMaps;
1881 for (const auto &textureInput : texturesInputs) {
1882 if (auto texture = textureInput->texture()) {
1883 if (texture->sourceItem()) {
1884 subsceneRootItem = texture->sourceItem();
1885 break;
1886 }
1887 }
1888 }
1889 }
1890 }
1891 return subsceneRootItem;
1892}
1893
1894
1895/*!
1896 \internal
1897*/
1898QQuick3DPickResult QQuick3DViewport::getNearestPickResult(const QVarLengthArray<QSSGRenderPickResult, 20> &pickResults) const
1899{
1900 for (const auto &result : pickResults) {
1901 auto pickResult = processPickResult(result);
1902 if (pickResult.hitType() != QQuick3DPickResultEnums::HitType::Null)
1903 return pickResult;
1904 }
1905
1906 return QQuick3DPickResult();
1907}
1908
1909/*!
1910 \internal
1911 This method is responsible for going through the visitedSubscenes map and forwarding
1912 the event with corrected coordinates to each subscene.
1913
1914*/
1915bool QQuick3DViewport::forwardEventToSubscenes(QPointerEvent *event,
1916 bool useRayPicking,
1917 QQuick3DSceneRenderer *renderer,
1918 const QFlatMap<QQuickItem *, SubsceneInfo> &visitedSubscenes) const
1919{
1920 // Now deliver the entire event (all points) to each relevant subscene.
1921 // Maybe only some points fall inside, but QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem()
1922 // makes reduced-subset touch events that contain only the relevant points, when necessary.
1923 bool ret = false;
1924
1925 QVarLengthArray<QPointF, 16> originalScenePositions;
1926 originalScenePositions.resize(event->pointCount());
1927 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
1928 originalScenePositions[pointIndex] = event->point(pointIndex).scenePosition();
1929 for (const auto &subscene : visitedSubscenes) {
1930 QQuickItem *subsceneRoot = subscene.first;
1931 const auto &subsceneInfo = subscene.second;
1932 Q_ASSERT(subsceneInfo.eventPointScenePositions.size() == event->pointCount());
1933 auto da = QQuickItemPrivate::get(subsceneRoot)->deliveryAgent();
1934 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
1935 const auto &pt = subsceneInfo.eventPointScenePositions.at(pointIndex);
1936 // By tradition, QGuiApplicationPrivate::processTouchEvent() has set the local position to the scene position,
1937 // and Qt Quick expects it to arrive that way: then QQuickDeliveryAgentPrivate::translateTouchEvent()
1938 // copies it into the scene position before localizing.
1939 // That might be silly, we might change it eventually, but gotta stay consistent for now.
1940 QEventPoint &ep = event->point(pointIndex);
1941 QMutableEventPoint::setPosition(ep, pt);
1942 QMutableEventPoint::setScenePosition(ep, pt);
1943 }
1944
1945 if (event->isBeginEvent())
1946 da->setSceneTransform(nullptr);
1947 if (da->event(event)) {
1948 ret = true;
1949 if (QQuickDeliveryAgentPrivate::anyPointGrabbed(event) && !useRayPicking) {
1950 // In case any QEventPoint was grabbed, the relevant QQuickDeliveryAgent needs to know
1951 // how to repeat the picking/coordinate transformation for each update,
1952 // because delivery will bypass internalPick() due to the grab, and it's
1953 // more efficient to avoid whole-scene picking each time anyway.
1954 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(subsceneInfo.obj);
1955 const bool item2Dcase = (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D);
1956 ViewportTransformHelper *transform = new ViewportTransformHelper;
1957 transform->viewport = const_cast<QQuick3DViewport *>(this);
1958 transform->renderer = renderer;
1959 transform->sceneParentNode = static_cast<QSSGRenderNode*>(frontendObjectPrivate->spatialNode);
1960 transform->targetItem = subsceneRoot;
1961 transform->scaleX = window()->effectiveDevicePixelRatio() * m_widthMultiplier;
1962 transform->scaleY = window()->effectiveDevicePixelRatio() * m_heightMultiplier;
1963 transform->uvCoordsArePixels = item2Dcase;
1964 transform->setOnDeliveryAgent(da);
1965 qCDebug(lcPick) << event->type() << "created ViewportTransformHelper on" << da;
1966 }
1967 } else if (event->type() != QEvent::HoverMove) {
1968 qCDebug(lcPick) << subsceneRoot << "didn't want" << event;
1969 }
1970 event->setAccepted(false); // reject implicit grab and let it keep propagating
1971 }
1972 if (visitedSubscenes.isEmpty()) {
1973 event->setAccepted(false);
1974 } else {
1975 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
1976 QMutableEventPoint::setScenePosition(event->point(pointIndex), originalScenePositions.at(pointIndex));
1977 }
1978
1979 // Normally this would occur in QQuickWindowPrivate::clearGrabbers(...) but
1980 // for ray based input, input never goes through QQuickWindow (since events
1981 // are generated from within scene space and not window/screen space).
1982 if (event->isEndEvent() && useRayPicking) {
1983 if (event->isSinglePointEvent()) {
1984 if (static_cast<QSinglePointEvent *>(event)->buttons() == Qt::NoButton) {
1985 auto &firstPt = event->point(0);
1986 event->setExclusiveGrabber(firstPt, nullptr);
1987 event->clearPassiveGrabbers(firstPt);
1988 }
1989 } else {
1990 for (auto &point : event->points()) {
1991 if (point.state() == QEventPoint::State::Released) {
1992 event->setExclusiveGrabber(point, nullptr);
1993 event->clearPassiveGrabbers(point);
1994 }
1995 }
1996 }
1997 }
1998
1999 return ret;
2000}
2001
2002
2003bool QQuick3DViewport::internalPick(QPointerEvent *event, const QVector3D &origin, const QVector3D &direction) const
2004{
2005 QQuick3DSceneRenderer *renderer = getRenderer();
2006 if (!renderer || !event)
2007 return false;
2008
2009 QFlatMap<QQuickItem*, SubsceneInfo> visitedSubscenes;
2010 const bool useRayPicking = !direction.isNull();
2011
2012 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
2013 auto &eventPoint = event->point(pointIndex);
2014 QVarLengthArray<QSSGRenderPickResult, 20> pickResults;
2015 if (Q_UNLIKELY(useRayPicking))
2016 pickResults = getPickResults(renderer, origin, direction);
2017 else
2018 pickResults = getPickResults(renderer, eventPoint);
2019
2020 if (!pickResults.isEmpty())
2021 for (const auto &pickResult : pickResults)
2022 processPickedObject(pickResult, pointIndex, event, visitedSubscenes);
2023 else
2024 eventPoint.setAccepted(false); // let it fall through the viewport to Items underneath
2025 }
2026
2027 return forwardEventToSubscenes(event, useRayPicking, renderer, visitedSubscenes);
2028}
2029
2030bool QQuick3DViewport::singlePointPick(QSinglePointEvent *event, const QVector3D &origin, const QVector3D &direction)
2031{
2032 QQuick3DSceneRenderer *renderer = getRenderer();
2033 if (!renderer || !event)
2034 return false;
2035
2036 QSSGRenderRay ray(origin, direction);
2037
2038 Q_ASSERT(event->pointCount() == 1);
2039 QPointF originalPosition = event->point(0).scenePosition();
2040
2041 auto pickResults = renderer->syncPickAll(ray);
2042
2043 bool delivered = false;
2044
2045 constexpr float jitterLimit = 2.5; // TODO: add property for this?
2046 bool withinJitterLimit = false;
2047
2048 for (const auto &pickResult : pickResults) {
2049 auto [item, position] = getItemAndPosition(pickResult);
2050 if (!item)
2051 break;
2052 if (item == m_prevMouseItem && (position - m_prevMousePos).manhattanLength() < jitterLimit && !event->button()) {
2053 withinJitterLimit = true;
2054 break;
2055 }
2056 auto da = QQuickItemPrivate::get(item)->deliveryAgent();
2057 QEventPoint &ep = event->point(0);
2058 QMutableEventPoint::setPosition(ep, position);
2059 QMutableEventPoint::setScenePosition(ep, position);
2060 if (da->event(event)) {
2061 delivered = true;
2062 if (event->type() == QEvent::MouseButtonPress) {
2063 m_prevMouseItem = item;
2064 m_prevMousePos = position;
2065 withinJitterLimit = true;
2066 }
2067 break;
2068 }
2069 }
2070
2071 QMutableEventPoint::setScenePosition(event->point(0), originalPosition);
2072 if (!withinJitterLimit)
2073 m_prevMouseItem = nullptr;
2074
2075 // Normally this would occur in QQuickWindowPrivate::clearGrabbers(...) but
2076 // for ray based input, input never goes through QQuickWindow (since events
2077 // are generated from within scene space and not window/screen space).
2078 if (event->isEndEvent()) {
2079 if (event->buttons() == Qt::NoButton) {
2080 auto &firstPt = event->point(0);
2081 event->setExclusiveGrabber(firstPt, nullptr);
2082 event->clearPassiveGrabbers(firstPt);
2083 }
2084 }
2085
2086 return delivered;
2087}
2088
2089QPair<QQuickItem *, QPointF> QQuick3DViewport::getItemAndPosition(const QSSGRenderPickResult &pickResult) const
2090{
2091 QQuickItem *subsceneRootItem = nullptr;
2092 QPointF subscenePosition;
2093 const auto backendObject = pickResult.m_hitObject;
2094 const auto frontendObject = findFrontendNode(backendObject);
2095 if (!frontendObject)
2096 return {};
2097 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(frontendObject);
2098 if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
2099 // Item2D, this is the case where there is just an embedded Qt Quick 2D Item
2100 // rendered directly to the scene.
2101 auto item2D = qobject_cast<QQuick3DItem2D *>(frontendObject);
2102 if (item2D)
2103 subsceneRootItem = item2D->contentItem();
2104 if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
2105 return {}; // ignore empty 2D subscenes
2106
2107 // In this case the "UV" coordinates are in pixels in the subscene root item's coordinate system.
2108 subscenePosition = pickResult.m_localUVCoords.toPointF();
2109
2110 // The following code will account for custom input masking, as well any
2111 // transformations that might have been applied to the Item
2112 if (!subsceneRootItem->childAt(subscenePosition.x(), subscenePosition.y()))
2113 return {};
2114 } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
2115 // Model
2116 int materialSubset = pickResult.m_subset;
2117 const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
2118 // Get material
2119 if (backendModel->materials.size() < (pickResult.m_subset + 1))
2120 materialSubset = backendModel->materials.size() - 1;
2121 if (materialSubset < 0)
2122 return {};
2123 const auto backendMaterial = backendModel->materials.at(materialSubset);
2124 const auto frontendMaterial = static_cast<QQuick3DMaterial *>(findFrontendNode(backendMaterial));
2125 subsceneRootItem = getSubSceneRootItem(frontendMaterial);
2126
2127 if (subsceneRootItem) {
2128 // In this case the pick result really is using UV coordinates.
2129 subscenePosition = QPointF(subsceneRootItem->x() + pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
2130 subsceneRootItem->y() - pickResult.m_localUVCoords.y() * subsceneRootItem->height() + subsceneRootItem->height());
2131 }
2132 }
2133 return {subsceneRootItem, subscenePosition};
2134}
2135
2136QVarLengthArray<QSSGRenderPickResult, 20> QQuick3DViewport::getPickResults(QQuick3DSceneRenderer *renderer,
2137 const QVector3D &origin,
2138 const QVector3D &direction) const
2139{
2140 const QSSGRenderRay ray(origin, direction);
2141 return renderer->syncPickAll(ray);
2142}
2143
2144QVarLengthArray<QSSGRenderPickResult, 20> QQuick3DViewport::getPickResults(QQuick3DSceneRenderer *renderer, const QEventPoint &eventPoint) const
2145{
2146 QVarLengthArray<QSSGRenderPickResult, 20> pickResults;
2147 QPointF realPosition = eventPoint.position() * window()->effectiveDevicePixelRatio();
2148 // correct when mapping is not 1:1 due to explicit backing texture size
2149 realPosition.rx() *= m_widthMultiplier;
2150 realPosition.ry() *= m_heightMultiplier;
2151 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(realPosition);
2152 if (rayResult.has_value())
2153 pickResults = renderer->syncPickAll(rayResult.value());
2154 return pickResults;
2155}
2156
2157/*!
2158 \internal
2159
2160 This provides a way to lookup frontendNodes with a backend node taking into consideration both
2161 the scene and the importScene
2162*/
2163QQuick3DObject *QQuick3DViewport::findFrontendNode(const QSSGRenderGraphObject *backendObject) const
2164{
2165 if (!backendObject)
2166 return nullptr;
2167
2168 const auto sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
2169 QQuick3DObject *frontendObject = sceneManager->lookUpNode(backendObject);
2170 if (!frontendObject && m_importScene) {
2171 const auto importSceneManager = QQuick3DObjectPrivate::get(m_importScene)->sceneManager;
2172 frontendObject = importSceneManager->lookUpNode(backendObject);
2173 }
2174 return frontendObject;
2175}
2176
2177QQuick3DPickResult QQuick3DViewport::processPickResult(const QSSGRenderPickResult &pickResult) const
2178{
2179 if (!pickResult.m_hitObject)
2180 return QQuick3DPickResult();
2181
2182 QQuick3DObject *frontendObject = findFrontendNode(pickResult.m_hitObject);
2183
2184 QQuick3DModel *model = qobject_cast<QQuick3DModel *>(frontendObject);
2185 if (model) {
2186 auto itemAndPosition = getItemAndPosition(pickResult);
2187 return QQuick3DPickResult(model,
2188 ::sqrtf(pickResult.m_distanceSq),
2189 pickResult.m_localUVCoords,
2190 pickResult.m_scenePosition,
2191 pickResult.m_localPosition,
2192 pickResult.m_faceNormal,
2193 pickResult.m_sceneNormal,
2194 pickResult.m_instanceIndex,
2195 itemAndPosition.first);
2196 }
2197
2198 QQuick3DItem2D *frontend2DItem = qobject_cast<QQuick3DItem2D *>(frontendObject);
2199 if (frontend2DItem && frontend2DItem->contentItem()) {
2200 // Check if the pick is inside the content item (since the ray just intersected on the items plane)
2201 const QPointF subscenePosition = pickResult.m_localUVCoords.toPointF();
2202 const auto child = frontend2DItem->contentItem()->childAt(subscenePosition.x(), subscenePosition.y());
2203 if (child) {
2204 return QQuick3DPickResult(child,
2205 ::sqrtf(pickResult.m_distanceSq),
2206 QVector2D(frontend2DItem->contentItem()->mapToItem(child, subscenePosition)),
2207 pickResult.m_scenePosition,
2208 pickResult.m_localPosition,
2209 pickResult.m_faceNormal);
2210 }
2211 }
2212
2213 return QQuick3DPickResult();
2214
2215}
2216
2217// Returns the first found scene manager of objects children
2218QQuick3DSceneManager *QQuick3DViewport::findChildSceneManager(QQuick3DObject *inObject, QQuick3DSceneManager *manager)
2219{
2220 if (manager)
2221 return manager;
2222
2223 auto children = QQuick3DObjectPrivate::get(inObject)->childItems;
2224 for (auto *child : std::as_const(children)) {
2225 if (auto m = QQuick3DObjectPrivate::get(child)->sceneManager) {
2226 manager = m;
2227 break;
2228 }
2229 manager = findChildSceneManager(child, manager);
2230 }
2231 return manager;
2232}
2233
2234void QQuick3DViewport::updateInputProcessing()
2235{
2236 // This should be called from the gui thread.
2237 setAcceptTouchEvents(m_enableInputProcessing);
2238 setAcceptHoverEvents(m_enableInputProcessing);
2239 setAcceptedMouseButtons(m_enableInputProcessing ? Qt::AllButtons : Qt::NoButton);
2240}
2241
2242void QQuick3DViewport::onReleaseCachedResources()
2243{
2244 if (auto renderer = getRenderer())
2245 renderer->releaseCachedResources();
2246}
2247
2248/*!
2249 \qmlproperty List<QtQuick3D::Object3D> View3D::extensions
2250
2251 This property contains a list of user extensions that should be used with this \l View3D.
2252
2253 \sa RenderExtension
2254*/
2255QQmlListProperty<QQuick3DObject> QQuick3DViewport::extensions()
2256{
2257 return QQmlListProperty<QQuick3DObject>{ this,
2258 &m_extensionListDirty,
2259 &QQuick3DExtensionListHelper::extensionAppend,
2260 &QQuick3DExtensionListHelper::extensionCount,
2261 &QQuick3DExtensionListHelper::extensionAt,
2262 &QQuick3DExtensionListHelper::extensionClear,
2263 &QQuick3DExtensionListHelper::extensionReplace,
2264 &QQuick3DExtensionListHelper::extensionRemoveLast};
2265}
2266
2267/*!
2268 \internal
2269 */
2270void QQuick3DViewport::rebuildExtensionList()
2271{
2272 m_extensionListDirty = true;
2273 update();
2274}
2275
2276/*!
2277 \internal
2278
2279 Private constructor for the QQuick3DViewport class so we can differentiate between
2280 a regular QQuick3DViewport and one created for a specific usage, like XR.
2281 */
2282QQuick3DViewport::QQuick3DViewport(PrivateInstanceType type, QQuickItem *parent)
2283 : QQuick3DViewport(parent)
2284{
2285 m_isXrViewInstance = type == PrivateInstanceType::XrViewInstance;
2286}
2287
2288void QQuick3DViewport::updateCameraForLayer(const QQuick3DViewport &view3D, QSSGRenderLayer &layerNode)
2289{
2290 layerNode.explicitCameras.clear();
2291 if (!view3D.m_multiViewCameras.isEmpty()) {
2292 for (QQuick3DCamera *camera : std::as_const(view3D.m_multiViewCameras))
2293 layerNode.explicitCameras.append(static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(camera)->spatialNode));
2294 } else if (view3D.camera()) {
2295 if (QSSGRenderCamera *camera = static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(view3D.camera())->spatialNode))
2296 layerNode.explicitCameras.append(camera);
2297 }
2298
2299 // Ensure these have a parent. All nodes need to be in the node tree somewhere, even if they're technically "parentless"
2300 // or we'll not assign a storage slot for them or update them.
2301 for (QSSGRenderCamera *camera : std::as_const(layerNode.explicitCameras)) {
2302 if (!camera->parent)
2303 layerNode.addChild(*camera);
2304 }
2305}
2306
2307void QQuick3DViewport::updateSceneManagerForImportScene()
2308{
2309 auto privateObject = QQuick3DObjectPrivate::get(m_importScene);
2310 if (!privateObject->sceneManager) {
2311 // If object doesn't already have scene manager, check from its children
2312 QQuick3DSceneManager *manager = findChildSceneManager(m_importScene);
2313 // If still not found, use the one from the scene root (scenes defined outside of an view3d)
2314 if (!manager)
2315 manager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
2316 if (manager) {
2317 manager->setWindow(window());
2318 privateObject->refSceneManager(*manager);
2319 }
2320
2321 // In the case m_sceneRoot doesn't have a valid sceneManager,
2322 // it means the view3d is not valid now.
2323 if (!privateObject->sceneManager)
2324 return;
2325
2326 }
2327 connect(privateObject->sceneManager, &QQuick3DSceneManager::needsUpdate,
2328 this, &QQuickItem::update, Qt::UniqueConnection);
2329 connect(privateObject->sceneManager, &QObject::destroyed,
2330 this, [&](QObject *) {
2331 auto privateObject = QQuick3DObjectPrivate::get(m_importScene);
2332 privateObject->sceneManager = nullptr;
2333 updateSceneManagerForImportScene();
2334 }, Qt::DirectConnection);
2335
2336 QQuick3DNode *scene = m_importScene;
2337 while (scene) {
2338 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
2339 scene = rn ? rn->view3D()->importScene() : nullptr;
2340
2341 if (scene) {
2342 connect(QQuick3DObjectPrivate::get(scene)->sceneManager,
2343 &QQuick3DSceneManager::needsUpdate,
2344 this, &QQuickItem::update, Qt::UniqueConnection);
2345 }
2346 }
2347}
2348
2349/*!
2350 \qmlproperty enumeration QtQuick3D::View3D::renderOverrides
2351
2352 Controls how Qt Quick 3D performs rendering.
2353
2354 \value View3D.None Rendering proceeds normally. Internal passes are scheduled as required by the
2355 features used in the application.
2356
2357 \value View3D.DisableInternalPasses Disables Qt Quick 3D's internal rendering passes.
2358 When this mode is set, the application is responsible for producing and presenting the final frame.
2359 Typically, this involves implementing custom \l {User passes} and presenting the result to the
2360 viewport using \l {SimpleQuadRenderer} or a custom \l {QQuick3DRenderExtension}{render extension}.
2361*/
2362
2363QQuick3DViewport::RenderOverrides QQuick3DViewport::renderOverrides() const
2364{
2365 return m_renderOverrides;
2366}
2367
2368void QQuick3DViewport::setRenderOverrides(RenderOverrides newRenderOverrides)
2369{
2370 if (m_renderOverrides == newRenderOverrides)
2371 return;
2372 m_renderOverrides = newRenderOverrides;
2373 emit renderOverridesChanged();
2374
2375 update();
2376}
2377
2378QT_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