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::}{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::}{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 m_renderNode = nullptr;
1590}
1591
1592QQuick3DSceneRenderer *QQuick3DViewport::getRenderer() const
1593{
1594 QQuick3DSceneRenderer *renderer = nullptr;
1595 if (m_node) {
1596 renderer = m_node->renderer;
1597 } else if (m_renderNode) {
1598 renderer = m_renderNode->renderer;
1599 } else if (m_directRenderer) {
1600 renderer = m_directRenderer->renderer();
1601 }
1602 return renderer;
1603}
1604
1605void QQuick3DViewport::updateDynamicTextures()
1606{
1607 // Update QSGDynamicTextures that are used for source textures and Quick items
1608 // Must be called on the render thread.
1609
1610 const auto &sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
1611 for (auto *texture : std::as_const(sceneManager->qsgDynamicTextures))
1612 texture->updateTexture();
1613
1614 QQuick3DNode *scene = m_importScene;
1615 while (scene) {
1616 const auto &importSm = QQuick3DObjectPrivate::get(scene)->sceneManager;
1617 if (importSm != sceneManager) {
1618 for (auto *texture : std::as_const(importSm->qsgDynamicTextures))
1619 texture->updateTexture();
1620 }
1621
1622 // if importScene has another import
1623 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
1624 scene = rn ? rn->view3D()->importScene() : nullptr;
1625 }
1626}
1627
1628QSGNode *QQuick3DViewport::setupOffscreenRenderer(QSGNode *node)
1629{
1630 SGFramebufferObjectNode *n = static_cast<SGFramebufferObjectNode *>(node);
1631
1632 if (!n) {
1633 if (!m_node)
1634 m_node = new SGFramebufferObjectNode;
1635 n = m_node;
1636 }
1637
1638 if (!n->renderer) {
1639 n->window = window();
1640 n->renderer = createRenderer();
1641 if (!n->renderer)
1642 return nullptr;
1643 n->renderer->fboNode = n;
1644 n->quickFbo = this;
1645 connect(window(), SIGNAL(screenChanged(QScreen*)), n, SLOT(handleScreenChange()));
1646 }
1647
1648 const qreal dpr = window()->effectiveDevicePixelRatio();
1649 const QSize minFboSize = QQuickItemPrivate::get(this)->sceneGraphContext()->minimumFBOSize();
1650 QSize desiredFboSize = QSize(m_explicitTextureWidth, m_explicitTextureHeight);
1651 if (desiredFboSize.isEmpty()) {
1652 desiredFboSize = QSize(width(), height()) * dpr;
1653 n->devicePixelRatio = dpr;
1654 // 1:1 mapping between the backing texture and the on-screen quad
1655 m_widthMultiplier = 1.0f;
1656 m_heightMultiplier = 1.0f;
1657 } else {
1658 QSize itemPixelSize = QSize(width(), height()) * dpr;
1659 // not 1:1 maping between the backing texture and the on-screen quad
1660 m_widthMultiplier = desiredFboSize.width() / float(itemPixelSize.width());
1661 m_heightMultiplier = desiredFboSize.height() / float(itemPixelSize.height());
1662 n->devicePixelRatio = 1.0;
1663 }
1664 desiredFboSize.setWidth(qMax(minFboSize.width(), desiredFboSize.width()));
1665 desiredFboSize.setHeight(qMax(minFboSize.height(), desiredFboSize.height()));
1666
1667 if (desiredFboSize != m_effectiveTextureSize) {
1668 m_effectiveTextureSize = desiredFboSize;
1669 emit effectiveTextureSizeChanged();
1670 }
1671
1672 n->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
1673 n->setRect(0, 0, width(), height());
1674 if (checkIsVisible() && isComponentComplete()) {
1675 n->renderer->synchronize(this, desiredFboSize, n->devicePixelRatio);
1676 if (n->renderer->m_textureNeedsFlip)
1677 n->setTextureCoordinatesTransform(QSGSimpleTextureNode::MirrorVertically);
1678 updateDynamicTextures();
1679 n->scheduleRender();
1680 }
1681
1682 return n;
1683}
1684
1685QSGNode *QQuick3DViewport::setupInlineRenderer(QSGNode *node)
1686{
1687 QQuick3DSGRenderNode *n = static_cast<QQuick3DSGRenderNode *>(node);
1688 if (!n) {
1689 if (!m_renderNode)
1690 m_renderNode = new QQuick3DSGRenderNode;
1691 n = m_renderNode;
1692 }
1693
1694 if (!n->renderer) {
1695 n->window = window();
1696 n->renderer = createRenderer();
1697 if (!n->renderer)
1698 return nullptr;
1699 }
1700
1701 if (!m_effectiveTextureSize.isEmpty()) {
1702 m_effectiveTextureSize = QSize();
1703 emit effectiveTextureSizeChanged();
1704 }
1705
1706 const QSize targetSize = window()->effectiveDevicePixelRatio() * QSize(width(), height());
1707
1708 // checkIsVisible, not isVisible, because, for example, a
1709 // { visible: false; layer.enabled: true } item still needs
1710 // to function normally.
1711 if (checkIsVisible() && isComponentComplete()) {
1712 n->renderer->synchronize(this, targetSize, window()->effectiveDevicePixelRatio());
1713 updateDynamicTextures();
1714 n->markDirty(QSGNode::DirtyMaterial);
1715 }
1716
1717 return n;
1718}
1719
1720
1721void QQuick3DViewport::setupDirectRenderer(RenderMode mode)
1722{
1723 auto renderMode = (mode == Underlay) ? QQuick3DSGDirectRenderer::Underlay
1724 : QQuick3DSGDirectRenderer::Overlay;
1725 if (!m_directRenderer) {
1726 QQuick3DSceneRenderer *sceneRenderer = createRenderer();
1727 if (!sceneRenderer)
1728 return;
1729 m_directRenderer = new QQuick3DSGDirectRenderer(sceneRenderer, window(), renderMode);
1730 connect(window(), &QQuickWindow::sceneGraphInvalidated, this, &QQuick3DViewport::cleanupDirectRenderer, Qt::DirectConnection);
1731 }
1732
1733 if (!m_effectiveTextureSize.isEmpty()) {
1734 m_effectiveTextureSize = QSize();
1735 emit effectiveTextureSizeChanged();
1736 }
1737
1738 const QSizeF targetSize = window()->effectiveDevicePixelRatio() * QSizeF(width(), height());
1739 m_directRenderer->setViewport(QRectF(window()->effectiveDevicePixelRatio() * mapToScene(QPointF(0, 0)), targetSize));
1740 m_directRenderer->setVisibility(isVisible());
1741 if (isVisible()) {
1742 m_directRenderer->preSynchronize();
1743 m_directRenderer->renderer()->synchronize(this, targetSize.toSize(), window()->effectiveDevicePixelRatio());
1744 updateDynamicTextures();
1745 m_directRenderer->requestRender();
1746 }
1747}
1748
1749// This is used for offscreen mode since we need to check if
1750// this item is used by an effect but hidden
1751bool QQuick3DViewport::checkIsVisible() const
1752{
1753 auto childPrivate = QQuickItemPrivate::get(this);
1754 return (childPrivate->explicitVisible ||
1755 (childPrivate->extra.isAllocated() && childPrivate->extra->effectRefCount));
1756
1757}
1758
1759/*!
1760 \internal
1761
1762 This method processes a pickResult on an object with the objective of checking
1763 if there are any QQuickItem based subscenes that the QPointerEvent needs to be
1764 forwarded to (3D -> 2D). If such a subscene is found, the event will be mapped
1765 to the correct cordinate system, and added to the visitedSubscenes map for later
1766 event delivery.
1767*/
1768void QQuick3DViewport::processPickedObject(const QSSGRenderPickResult &pickResult,
1769 int pointIndex,
1770 QPointerEvent *event,
1771 QFlatMap<QQuickItem *, SubsceneInfo> &visitedSubscenes) const
1772{
1773 QQuickItem *subsceneRootItem = nullptr;
1774 QPointF subscenePosition;
1775 const auto backendObject = pickResult.m_hitObject;
1776 const auto frontendObject = findFrontendNode(backendObject);
1777 if (!frontendObject)
1778 return;
1779
1780 // Figure out if there are any QQuickItem based scenes we need to forward
1781 // the event to, and if the are found, determine how to translate the UV Coords
1782 // in the pickResult based on what type the object containing the scene is.
1783 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(frontendObject);
1784 if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
1785 // Item2D, this is the case where there is just an embedded Qt Quick 2D Item
1786 // rendered directly to the scene.
1787 auto item2D = qobject_cast<QQuick3DItem2D *>(frontendObject);
1788 if (item2D)
1789 subsceneRootItem = item2D->contentItem();
1790 if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
1791 return; // ignore empty 2D subscenes
1792
1793 // In this case the "UV" coordinates are in pixels in the subscene root item's coordinate system.
1794 subscenePosition = pickResult.m_localUVCoords.toPointF();
1795
1796 // The following code will account for custom input masking, as well any
1797 // transformations that might have been applied to the Item
1798 if (!subsceneRootItem->childAt(subscenePosition.x(), subscenePosition.y()))
1799 return;
1800 } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
1801 // Model
1802 int materialSubset = pickResult.m_subset;
1803 const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
1804 // Get material
1805 if (backendModel->materials.size() < (pickResult.m_subset + 1))
1806 materialSubset = backendModel->materials.size() - 1;
1807 if (materialSubset < 0)
1808 return;
1809 const auto backendMaterial = backendModel->materials.at(materialSubset);
1810 const auto frontendMaterial = static_cast<QQuick3DMaterial*>(findFrontendNode(backendMaterial));
1811 subsceneRootItem = getSubSceneRootItem(frontendMaterial);
1812
1813 if (subsceneRootItem) {
1814 // In this case the pick result really is using UV coordinates.
1815 subscenePosition = QPointF(subsceneRootItem->x() + pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
1816 subsceneRootItem->y() - pickResult.m_localUVCoords.y() * subsceneRootItem->height() + subsceneRootItem->height());
1817 }
1818 }
1819
1820 // Add the new event (item and position) to the visitedSubscene map.
1821 if (subsceneRootItem) {
1822 SubsceneInfo &subscene = visitedSubscenes[subsceneRootItem]; // create if not found
1823 subscene.obj = frontendObject;
1824 if (subscene.eventPointScenePositions.size() != event->pointCount()) {
1825 // ensure capacity, and use an out-of-scene position rather than 0,0 by default
1826 constexpr QPointF inf(-qt_inf(), -qt_inf());
1827 subscene.eventPointScenePositions.resize(event->pointCount(), inf);
1828 }
1829 subscene.eventPointScenePositions[pointIndex] = subscenePosition;
1830 }
1831}
1832
1833/*!
1834 \internal
1835
1836 This method will try and find a QQuickItem based by looking for sourceItem()
1837 properties on the primary color channels for the various material types.
1838
1839 \note for CustomMaterial there is just a best effort given where the first
1840 associated Texture with a sourceItem property is used.
1841*/
1842
1843QQuickItem *QQuick3DViewport::getSubSceneRootItem(QQuick3DMaterial *material) const
1844{
1845 if (!material)
1846 return nullptr;
1847
1848 QQuickItem *subsceneRootItem = nullptr;
1849 const auto frontendMaterialPrivate = QQuick3DObjectPrivate::get(material);
1850
1851 if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::DefaultMaterial) {
1852 // Default Material
1853 const auto defaultMaterial = qobject_cast<QQuick3DDefaultMaterial *>(material);
1854 if (defaultMaterial) {
1855 // Just check for a diffuseMap for now
1856 if (defaultMaterial->diffuseMap() && defaultMaterial->diffuseMap()->sourceItem())
1857 subsceneRootItem = defaultMaterial->diffuseMap()->sourceItem();
1858 }
1859
1860 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::PrincipledMaterial) {
1861 // Principled Material
1862 const auto principledMaterial = qobject_cast<QQuick3DPrincipledMaterial *>(material);
1863 if (principledMaterial) {
1864 // Just check for a baseColorMap for now
1865 if (principledMaterial->baseColorMap() && principledMaterial->baseColorMap()->sourceItem())
1866 subsceneRootItem = principledMaterial->baseColorMap()->sourceItem();
1867 }
1868 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::SpecularGlossyMaterial) {
1869 // SpecularGlossy Material
1870 const auto specularGlossyMaterial = qobject_cast<QQuick3DSpecularGlossyMaterial *>(material);
1871 if (specularGlossyMaterial) {
1872 // Just check for a albedoMap for now
1873 if (specularGlossyMaterial->albedoMap() && specularGlossyMaterial->albedoMap()->sourceItem())
1874 subsceneRootItem = specularGlossyMaterial->albedoMap()->sourceItem();
1875 }
1876 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::CustomMaterial) {
1877 // Custom Material
1878 const auto customMaterial = qobject_cast<QQuick3DCustomMaterial *>(material);
1879 if (customMaterial) {
1880 // This case is a bit harder because we can not know how the textures will be used
1881 const auto &texturesInputs = customMaterial->m_dynamicTextureMaps;
1882 for (const auto &textureInput : texturesInputs) {
1883 if (auto texture = textureInput->texture()) {
1884 if (texture->sourceItem()) {
1885 subsceneRootItem = texture->sourceItem();
1886 break;
1887 }
1888 }
1889 }
1890 }
1891 }
1892 return subsceneRootItem;
1893}
1894
1895
1896/*!
1897 \internal
1898*/
1899QQuick3DPickResult QQuick3DViewport::getNearestPickResult(const QVarLengthArray<QSSGRenderPickResult, 20> &pickResults) const
1900{
1901 for (const auto &result : pickResults) {
1902 auto pickResult = processPickResult(result);
1903 if (pickResult.hitType() != QQuick3DPickResultEnums::HitType::Null)
1904 return pickResult;
1905 }
1906
1907 return QQuick3DPickResult();
1908}
1909
1910/*!
1911 \internal
1912 This method is responsible for going through the visitedSubscenes map and forwarding
1913 the event with corrected coordinates to each subscene.
1914
1915*/
1916bool QQuick3DViewport::forwardEventToSubscenes(QPointerEvent *event,
1917 bool useRayPicking,
1918 QQuick3DSceneRenderer *renderer,
1919 const QFlatMap<QQuickItem *, SubsceneInfo> &visitedSubscenes) const
1920{
1921 // Now deliver the entire event (all points) to each relevant subscene.
1922 // Maybe only some points fall inside, but QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem()
1923 // makes reduced-subset touch events that contain only the relevant points, when necessary.
1924 bool ret = false;
1925
1926 QVarLengthArray<QPointF, 16> originalScenePositions;
1927 originalScenePositions.resize(event->pointCount());
1928 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
1929 originalScenePositions[pointIndex] = event->point(pointIndex).scenePosition();
1930 for (const auto &subscene : visitedSubscenes) {
1931 QQuickItem *subsceneRoot = subscene.first;
1932 const auto &subsceneInfo = subscene.second;
1933 Q_ASSERT(subsceneInfo.eventPointScenePositions.size() == event->pointCount());
1934 auto da = QQuickItemPrivate::get(subsceneRoot)->deliveryAgent();
1935 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
1936 const auto &pt = subsceneInfo.eventPointScenePositions.at(pointIndex);
1937 // By tradition, QGuiApplicationPrivate::processTouchEvent() has set the local position to the scene position,
1938 // and Qt Quick expects it to arrive that way: then QQuickDeliveryAgentPrivate::translateTouchEvent()
1939 // copies it into the scene position before localizing.
1940 // That might be silly, we might change it eventually, but gotta stay consistent for now.
1941 QEventPoint &ep = event->point(pointIndex);
1942 QMutableEventPoint::setPosition(ep, pt);
1943 QMutableEventPoint::setScenePosition(ep, pt);
1944 }
1945
1946 if (event->isBeginEvent())
1947 da->setSceneTransform(nullptr);
1948 if (da->event(event)) {
1949 ret = true;
1950 if (QQuickDeliveryAgentPrivate::anyPointGrabbed(event) && !useRayPicking) {
1951 // In case any QEventPoint was grabbed, the relevant QQuickDeliveryAgent needs to know
1952 // how to repeat the picking/coordinate transformation for each update,
1953 // because delivery will bypass internalPick() due to the grab, and it's
1954 // more efficient to avoid whole-scene picking each time anyway.
1955 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(subsceneInfo.obj);
1956 const bool item2Dcase = (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D);
1957 ViewportTransformHelper *transform = new ViewportTransformHelper;
1958 transform->viewport = const_cast<QQuick3DViewport *>(this);
1959 transform->renderer = renderer;
1960 transform->sceneParentNode = static_cast<QSSGRenderNode*>(frontendObjectPrivate->spatialNode);
1961 transform->targetItem = subsceneRoot;
1962 transform->scaleX = window()->effectiveDevicePixelRatio() * m_widthMultiplier;
1963 transform->scaleY = window()->effectiveDevicePixelRatio() * m_heightMultiplier;
1964 transform->uvCoordsArePixels = item2Dcase;
1965 transform->setOnDeliveryAgent(da);
1966 qCDebug(lcPick) << event->type() << "created ViewportTransformHelper on" << da;
1967 }
1968 } else if (event->type() != QEvent::HoverMove) {
1969 qCDebug(lcPick) << subsceneRoot << "didn't want" << event;
1970 }
1971 event->setAccepted(false); // reject implicit grab and let it keep propagating
1972 }
1973 if (visitedSubscenes.isEmpty()) {
1974 event->setAccepted(false);
1975 } else {
1976 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
1977 QMutableEventPoint::setScenePosition(event->point(pointIndex), originalScenePositions.at(pointIndex));
1978 }
1979
1980 // Normally this would occur in QQuickWindowPrivate::clearGrabbers(...) but
1981 // for ray based input, input never goes through QQuickWindow (since events
1982 // are generated from within scene space and not window/screen space).
1983 if (event->isEndEvent() && useRayPicking) {
1984 if (event->isSinglePointEvent()) {
1985 if (static_cast<QSinglePointEvent *>(event)->buttons() == Qt::NoButton) {
1986 auto &firstPt = event->point(0);
1987 event->setExclusiveGrabber(firstPt, nullptr);
1988 event->clearPassiveGrabbers(firstPt);
1989 }
1990 } else {
1991 for (auto &point : event->points()) {
1992 if (point.state() == QEventPoint::State::Released) {
1993 event->setExclusiveGrabber(point, nullptr);
1994 event->clearPassiveGrabbers(point);
1995 }
1996 }
1997 }
1998 }
1999
2000 return ret;
2001}
2002
2003
2004bool QQuick3DViewport::internalPick(QPointerEvent *event, const QVector3D &origin, const QVector3D &direction) const
2005{
2006 QQuick3DSceneRenderer *renderer = getRenderer();
2007 if (!renderer || !event)
2008 return false;
2009
2010 QFlatMap<QQuickItem*, SubsceneInfo> visitedSubscenes;
2011 const bool useRayPicking = !direction.isNull();
2012
2013 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
2014 auto &eventPoint = event->point(pointIndex);
2015 QVarLengthArray<QSSGRenderPickResult, 20> pickResults;
2016 if (Q_UNLIKELY(useRayPicking))
2017 pickResults = getPickResults(renderer, origin, direction);
2018 else
2019 pickResults = getPickResults(renderer, eventPoint);
2020
2021 if (!pickResults.isEmpty())
2022 for (const auto &pickResult : pickResults)
2023 processPickedObject(pickResult, pointIndex, event, visitedSubscenes);
2024 else
2025 eventPoint.setAccepted(false); // let it fall through the viewport to Items underneath
2026 }
2027
2028 return forwardEventToSubscenes(event, useRayPicking, renderer, visitedSubscenes);
2029}
2030
2031bool QQuick3DViewport::singlePointPick(QSinglePointEvent *event, const QVector3D &origin, const QVector3D &direction)
2032{
2033 QQuick3DSceneRenderer *renderer = getRenderer();
2034 if (!renderer || !event)
2035 return false;
2036
2037 QSSGRenderRay ray(origin, direction);
2038
2039 Q_ASSERT(event->pointCount() == 1);
2040 QPointF originalPosition = event->point(0).scenePosition();
2041
2042 auto pickResults = renderer->syncPickAll(ray);
2043
2044 bool delivered = false;
2045
2046 constexpr float jitterLimit = 2.5; // TODO: add property for this?
2047 bool withinJitterLimit = false;
2048
2049 for (const auto &pickResult : pickResults) {
2050 auto [item, position] = getItemAndPosition(pickResult);
2051 if (!item)
2052 break;
2053 if (item == m_prevMouseItem && (position - m_prevMousePos).manhattanLength() < jitterLimit && !event->button()) {
2054 withinJitterLimit = true;
2055 break;
2056 }
2057 auto da = QQuickItemPrivate::get(item)->deliveryAgent();
2058 QEventPoint &ep = event->point(0);
2059 QMutableEventPoint::setPosition(ep, position);
2060 QMutableEventPoint::setScenePosition(ep, position);
2061 if (da->event(event)) {
2062 delivered = true;
2063 if (event->type() == QEvent::MouseButtonPress) {
2064 m_prevMouseItem = item;
2065 m_prevMousePos = position;
2066 withinJitterLimit = true;
2067 }
2068 break;
2069 }
2070 }
2071
2072 QMutableEventPoint::setScenePosition(event->point(0), originalPosition);
2073 if (!withinJitterLimit)
2074 m_prevMouseItem = nullptr;
2075
2076 // Normally this would occur in QQuickWindowPrivate::clearGrabbers(...) but
2077 // for ray based input, input never goes through QQuickWindow (since events
2078 // are generated from within scene space and not window/screen space).
2079 if (event->isEndEvent()) {
2080 if (event->buttons() == Qt::NoButton) {
2081 auto &firstPt = event->point(0);
2082 event->setExclusiveGrabber(firstPt, nullptr);
2083 event->clearPassiveGrabbers(firstPt);
2084 }
2085 }
2086
2087 return delivered;
2088}
2089
2090QPair<QQuickItem *, QPointF> QQuick3DViewport::getItemAndPosition(const QSSGRenderPickResult &pickResult) const
2091{
2092 QQuickItem *subsceneRootItem = nullptr;
2093 QPointF subscenePosition;
2094 const auto backendObject = pickResult.m_hitObject;
2095 const auto frontendObject = findFrontendNode(backendObject);
2096 if (!frontendObject)
2097 return {};
2098 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(frontendObject);
2099 if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
2100 // Item2D, this is the case where there is just an embedded Qt Quick 2D Item
2101 // rendered directly to the scene.
2102 auto item2D = qobject_cast<QQuick3DItem2D *>(frontendObject);
2103 if (item2D)
2104 subsceneRootItem = item2D->contentItem();
2105 if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
2106 return {}; // ignore empty 2D subscenes
2107
2108 // In this case the "UV" coordinates are in pixels in the subscene root item's coordinate system.
2109 subscenePosition = pickResult.m_localUVCoords.toPointF();
2110
2111 // The following code will account for custom input masking, as well any
2112 // transformations that might have been applied to the Item
2113 if (!subsceneRootItem->childAt(subscenePosition.x(), subscenePosition.y()))
2114 return {};
2115 } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
2116 // Model
2117 int materialSubset = pickResult.m_subset;
2118 const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
2119 // Get material
2120 if (backendModel->materials.size() < (pickResult.m_subset + 1))
2121 materialSubset = backendModel->materials.size() - 1;
2122 if (materialSubset < 0)
2123 return {};
2124 const auto backendMaterial = backendModel->materials.at(materialSubset);
2125 const auto frontendMaterial = static_cast<QQuick3DMaterial *>(findFrontendNode(backendMaterial));
2126 subsceneRootItem = getSubSceneRootItem(frontendMaterial);
2127
2128 if (subsceneRootItem) {
2129 // In this case the pick result really is using UV coordinates.
2130 subscenePosition = QPointF(subsceneRootItem->x() + pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
2131 subsceneRootItem->y() - pickResult.m_localUVCoords.y() * subsceneRootItem->height() + subsceneRootItem->height());
2132 }
2133 }
2134 return {subsceneRootItem, subscenePosition};
2135}
2136
2137QVarLengthArray<QSSGRenderPickResult, 20> QQuick3DViewport::getPickResults(QQuick3DSceneRenderer *renderer,
2138 const QVector3D &origin,
2139 const QVector3D &direction) const
2140{
2141 const QSSGRenderRay ray(origin, direction);
2142 return renderer->syncPickAll(ray);
2143}
2144
2145QVarLengthArray<QSSGRenderPickResult, 20> QQuick3DViewport::getPickResults(QQuick3DSceneRenderer *renderer, const QEventPoint &eventPoint) const
2146{
2147 QVarLengthArray<QSSGRenderPickResult, 20> pickResults;
2148 QPointF realPosition = eventPoint.position() * window()->effectiveDevicePixelRatio();
2149 // correct when mapping is not 1:1 due to explicit backing texture size
2150 realPosition.rx() *= m_widthMultiplier;
2151 realPosition.ry() *= m_heightMultiplier;
2152 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(realPosition);
2153 if (rayResult.has_value())
2154 pickResults = renderer->syncPickAll(rayResult.value());
2155 return pickResults;
2156}
2157
2158/*!
2159 \internal
2160
2161 This provides a way to lookup frontendNodes with a backend node taking into consideration both
2162 the scene and the importScene
2163*/
2164QQuick3DObject *QQuick3DViewport::findFrontendNode(const QSSGRenderGraphObject *backendObject) const
2165{
2166 if (!backendObject)
2167 return nullptr;
2168
2169 const auto sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
2170 QQuick3DObject *frontendObject = sceneManager->lookUpNode(backendObject);
2171 if (!frontendObject && m_importScene) {
2172 const auto importSceneManager = QQuick3DObjectPrivate::get(m_importScene)->sceneManager;
2173 frontendObject = importSceneManager->lookUpNode(backendObject);
2174 }
2175 return frontendObject;
2176}
2177
2178QQuick3DPickResult QQuick3DViewport::processPickResult(const QSSGRenderPickResult &pickResult) const
2179{
2180 if (!pickResult.m_hitObject)
2181 return QQuick3DPickResult();
2182
2183 QQuick3DObject *frontendObject = findFrontendNode(pickResult.m_hitObject);
2184
2185 QQuick3DModel *model = qobject_cast<QQuick3DModel *>(frontendObject);
2186 if (model) {
2187 auto itemAndPosition = getItemAndPosition(pickResult);
2188 return QQuick3DPickResult(model,
2189 ::sqrtf(pickResult.m_distanceSq),
2190 pickResult.m_localUVCoords,
2191 pickResult.m_scenePosition,
2192 pickResult.m_localPosition,
2193 pickResult.m_faceNormal,
2194 pickResult.m_sceneNormal,
2195 pickResult.m_instanceIndex,
2196 itemAndPosition.first);
2197 }
2198
2199 QQuick3DItem2D *frontend2DItem = qobject_cast<QQuick3DItem2D *>(frontendObject);
2200 if (frontend2DItem && frontend2DItem->contentItem()) {
2201 // Check if the pick is inside the content item (since the ray just intersected on the items plane)
2202 const QPointF subscenePosition = pickResult.m_localUVCoords.toPointF();
2203 const auto child = frontend2DItem->contentItem()->childAt(subscenePosition.x(), subscenePosition.y());
2204 if (child) {
2205 return QQuick3DPickResult(child,
2206 ::sqrtf(pickResult.m_distanceSq),
2207 QVector2D(frontend2DItem->contentItem()->mapToItem(child, subscenePosition)),
2208 pickResult.m_scenePosition,
2209 pickResult.m_localPosition,
2210 pickResult.m_faceNormal);
2211 }
2212 }
2213
2214 return QQuick3DPickResult();
2215
2216}
2217
2218// Returns the first found scene manager of objects children
2219QQuick3DSceneManager *QQuick3DViewport::findChildSceneManager(QQuick3DObject *inObject, QQuick3DSceneManager *manager)
2220{
2221 if (manager)
2222 return manager;
2223
2224 auto children = QQuick3DObjectPrivate::get(inObject)->childItems;
2225 for (auto *child : std::as_const(children)) {
2226 if (auto m = QQuick3DObjectPrivate::get(child)->sceneManager) {
2227 manager = m;
2228 break;
2229 }
2230 manager = findChildSceneManager(child, manager);
2231 }
2232 return manager;
2233}
2234
2235void QQuick3DViewport::updateInputProcessing()
2236{
2237 // This should be called from the gui thread.
2238 setAcceptTouchEvents(m_enableInputProcessing);
2239 setAcceptHoverEvents(m_enableInputProcessing);
2240 setAcceptedMouseButtons(m_enableInputProcessing ? Qt::AllButtons : Qt::NoButton);
2241}
2242
2243void QQuick3DViewport::onReleaseCachedResources()
2244{
2245 if (auto renderer = getRenderer())
2246 renderer->releaseCachedResources();
2247}
2248
2249/*!
2250 \qmlproperty List<QtQuick3D::Object3D> View3D::extensions
2251
2252 This property contains a list of user extensions that should be used with this \l View3D.
2253
2254 \sa RenderExtension
2255*/
2256QQmlListProperty<QQuick3DObject> QQuick3DViewport::extensions()
2257{
2258 return QQmlListProperty<QQuick3DObject>{ this,
2259 &m_extensionListDirty,
2260 &QQuick3DExtensionListHelper::extensionAppend,
2261 &QQuick3DExtensionListHelper::extensionCount,
2262 &QQuick3DExtensionListHelper::extensionAt,
2263 &QQuick3DExtensionListHelper::extensionClear,
2264 &QQuick3DExtensionListHelper::extensionReplace,
2265 &QQuick3DExtensionListHelper::extensionRemoveLast};
2266}
2267
2268/*!
2269 \internal
2270 */
2271void QQuick3DViewport::rebuildExtensionList()
2272{
2273 m_extensionListDirty = true;
2274 update();
2275}
2276
2277/*!
2278 \internal
2279
2280 Private constructor for the QQuick3DViewport class so we can differentiate between
2281 a regular QQuick3DViewport and one created for a specific usage, like XR.
2282 */
2283QQuick3DViewport::QQuick3DViewport(PrivateInstanceType type, QQuickItem *parent)
2284 : QQuick3DViewport(parent)
2285{
2286 m_isXrViewInstance = type == PrivateInstanceType::XrViewInstance;
2287}
2288
2289void QQuick3DViewport::updateCameraForLayer(const QQuick3DViewport &view3D, QSSGRenderLayer &layerNode)
2290{
2291 layerNode.explicitCameras.clear();
2292 if (!view3D.m_multiViewCameras.isEmpty()) {
2293 for (QQuick3DCamera *camera : std::as_const(view3D.m_multiViewCameras))
2294 layerNode.explicitCameras.append(static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(camera)->spatialNode));
2295 } else if (view3D.camera()) {
2296 if (QSSGRenderCamera *camera = static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(view3D.camera())->spatialNode))
2297 layerNode.explicitCameras.append(camera);
2298 }
2299
2300 // Ensure these have a parent. All nodes need to be in the node tree somewhere, even if they're technically "parentless"
2301 // or we'll not assign a storage slot for them or update them.
2302 for (QSSGRenderCamera *camera : std::as_const(layerNode.explicitCameras)) {
2303 if (!camera->parent)
2304 layerNode.addChild(*camera);
2305 }
2306}
2307
2308void QQuick3DViewport::updateSceneManagerForImportScene()
2309{
2310 auto privateObject = QQuick3DObjectPrivate::get(m_importScene);
2311 if (!privateObject->sceneManager) {
2312 // If object doesn't already have scene manager, check from its children
2313 QQuick3DSceneManager *manager = findChildSceneManager(m_importScene);
2314 // If still not found, use the one from the scene root (scenes defined outside of an view3d)
2315 if (!manager)
2316 manager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
2317 if (manager) {
2318 manager->setWindow(window());
2319 privateObject->refSceneManager(*manager);
2320 }
2321
2322 // In the case m_sceneRoot doesn't have a valid sceneManager,
2323 // it means the view3d is not valid now.
2324 if (!privateObject->sceneManager)
2325 return;
2326
2327 }
2328 connect(privateObject->sceneManager, &QQuick3DSceneManager::needsUpdate,
2329 this, &QQuickItem::update, Qt::UniqueConnection);
2330 connect(privateObject->sceneManager, &QObject::destroyed,
2331 this, [&](QObject *) {
2332 auto privateObject = QQuick3DObjectPrivate::get(m_importScene);
2333 privateObject->sceneManager = nullptr;
2334 updateSceneManagerForImportScene();
2335 }, Qt::DirectConnection);
2336
2337 QQuick3DNode *scene = m_importScene;
2338 while (scene) {
2339 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
2340 scene = rn ? rn->view3D()->importScene() : nullptr;
2341
2342 if (scene) {
2343 connect(QQuick3DObjectPrivate::get(scene)->sceneManager,
2344 &QQuick3DSceneManager::needsUpdate,
2345 this, &QQuickItem::update, Qt::UniqueConnection);
2346 }
2347 }
2348}
2349
2350/*!
2351 \qmlproperty enumeration QtQuick3D::View3D::renderOverrides
2352
2353 Controls how Qt Quick 3D performs rendering.
2354
2355 \value View3D.None Rendering proceeds normally. Internal passes are scheduled as required by the
2356 features used in the application.
2357
2358 \value View3D.DisableInternalPasses Disables Qt Quick 3D's internal rendering passes.
2359 When this mode is set, the application is responsible for producing and presenting the final frame.
2360 Typically, this involves implementing custom \l {User-Defined Render Passes in Qt Quick 3D}
2361 {user passes} and presenting the result to the viewport using \l {SimpleQuadRenderer} or a
2362 custom \l {QQuick3DRenderExtension}{render extension}.
2363*/
2364
2365QQuick3DViewport::RenderOverrides QQuick3DViewport::renderOverrides() const
2366{
2367 return m_renderOverrides;
2368}
2369
2370void QQuick3DViewport::setRenderOverrides(RenderOverrides newRenderOverrides)
2371{
2372 if (m_renderOverrides == newRenderOverrides)
2373 return;
2374 m_renderOverrides = newRenderOverrides;
2375 emit renderOverridesChanged();
2376
2377 update();
2378}
2379
2380QT_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