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