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