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
qquick3dxrview.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 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
9#include <QQuickWindow>
10#include <QQuickItem>
11#include <QLoggingCategory>
12
13#include <QtQuick3DUtils/private/qssgassert_p.h>
14
16
18
19Q_DECLARE_LOGGING_CATEGORY(lcQuick3DXr);
20
21/*!
22 \qmltype XrView
23 \inherits Node
24 \inqmlmodule QtQuick3D.Xr
25 \brief Sets up the view for an Xr application.
26
27 An XrView sets up the view for an XR application.
28 The following snippet is from the \l{\qxr Simple Example} and shows
29 how to use the type.
30
31 \quotefromfile xr_simple/main.qml
32 \printto XrOrigin
33
34 Unlike a \l View3D, an XrView is a \l Node and has no size in pixels: it
35 renders to the device, and the scene it contains is expressed in
36 centimeters. See \l{Coordinate Systems and Units in \qxr}.
37
38 \section1 Platform notes
39
40 \section2 Meta Quest Devices
41
42 To \l{XrView::passthroughEnabled}{enable passthrough} you need to add the
43 following permisson your app's \c AndroidManifest.xml file:
44
45 \badcode
46 <uses-feature android:name="com.oculus.feature.PASSTHROUGH" android:required="false"/>
47 \endcode
48
49*/
50
51QQuick3DXrView::QQuick3DXrView()
52 : m_xrRuntimeInfo(&m_xrManager)
53{
54 init();
55}
56
57QQuick3DXrView::~QQuick3DXrView()
58{
59 m_inDestructor = true;
60}
61
62/*!
63 \qmlproperty XrOrigin QtQuick3D.Xr::XrView::xrOrigin
64 \brief Holds the active XR origin.
65
66 The XR origin is the point in the scene that is considered the origin of the
67 XR coordinate system. The XR origin is used to position tracked objects like
68 the camera and controllers in the scene. An application can have multiple XrOrigins
69 but only one can be active at a time.
70
71 \note This property must be set for the scene to be rendered in XR.
72
73 \sa XrOrigin
74*/
75
76QQuick3DXrOrigin *QQuick3DXrView::xrOrigin() const
77{
78 return m_xrOrigin;
79}
80
81/*!
82 \qmlproperty SceneEnvironment QtQuick3D.Xr::XrView::environment
83 \summary Holds the SceneEnvironment for the XR view.
84*/
85
86QQuick3DSceneEnvironment *QQuick3DXrView::environment() const
87{
88 return m_xrManager.m_vrViewport ? m_xrManager.m_vrViewport->environment() : nullptr;
89}
90
91QQuick3DViewport *QQuick3DXrView::view3d() const
92{
93 return m_xrManager.m_vrViewport;
94}
95
96namespace {
97
98struct ClosestPointResult
99{
100 bool grab = false;
101 bool inside = false;
102 float distance = 0;
103 QVector3D position;
104 QVector3D scenePosition;
105 QVector3D normal;
106 QVector3D sceneNormal;
107 QVector2D uvPosition;
108 QPointF point2D;
109 QQuickItem *quickItem = nullptr;
110};
111
112// We can assume that the transform for an XrItem always has uniform scaling,
113// so the closest point in local space is also closest in scene space
114// We're not interested in hits from the side; but we probably want a margin around the item
115static ClosestPointResult closestPointOnXrItem(const QQuick3DXrItem *xrItem, const QVector3D &pos, const float margin = 0)
116{
117 const auto mappedPos = xrItem->mapPositionFromScene(pos);
118 const qreal width = xrItem->width();
119 const qreal height = xrItem->height();
120
121 const float x = mappedPos.x();
122 const float y = mappedPos.y();
123 const float z = mappedPos.z();
124
125 const float x1 = 0;
126 const float y1 = -height;
127 const float x2 = width;
128 const float y2 = 0;
129
130 const bool insideMargin = x1 - margin <= x && x <= x2 + margin && y1 - margin <= y && y <= y2 + margin;
131 if (!insideMargin)
132 return {};
133 const bool inside = x1 <= x && x <= x2 && y1 <= y && y <= y2 && z <= 0;
134
135 const float distance = mappedPos.z();
136
137 const QVector3D position{ mappedPos.x(), mappedPos.y(), 0 };
138 const QVector3D scenePosition = xrItem->mapPositionToScene(position);
139 const QVector3D normal{ 0, 0, 1 };
140 const QVector3D sceneNormal = xrItem->mapDirectionToScene(normal);
141
142 return {
143 true,
144 inside,
145 distance,
146 position,
147 scenePosition,
148 normal,
149 sceneNormal,
150 QVector2D{},
151 QPointF{mappedPos.x(), -mappedPos.y()},
152 xrItem->contentItem()
153 };
154}
155
156
157static ClosestPointResult closestPointOnModel(QQuick3DXrView *view, QQuick3DModel *model, const QVector3D &pos, const float radius = 0)
158{
159 auto pickResult = view->closestPointPick(pos, radius, model);
160 if (pickResult.objectHit() != model)
161 return {};
162
163 const QVector3D offset = pos - pickResult.scenePosition();
164
165 const float signedDistance = QVector3D::dotProduct(offset, pickResult.sceneNormal());
166 const bool inside = signedDistance < 0;
167
168 QPointF quickPoint;
169 QQuickItem *quickItem = pickResult.itemHit();
170 auto uv = pickResult.uvPosition();
171 if (quickItem) {
172 quickPoint = QPointF(quickItem->x() + uv.x() * quickItem->width(),
173 quickItem->y() - uv.y() * quickItem->height() + quickItem->height());
174 }
175 return {
176 true,
177 inside,
178 signedDistance,
179 pickResult.position(),
180 pickResult.scenePosition(),
181 pickResult.normal(),
182 pickResult.sceneNormal().normalized(), //#### TODO: fix sceneNormal
183 uv,
184 quickPoint,
185 quickItem
186 };
187}
188
189
190static inline ClosestPointResult closestPointOnItem(QQuick3DXrView *view, QQuick3DXrView::TouchTarget target, const QVector3D &pos, const float margin)
191{
192 if (auto *asXrItem = std::get_if<QQuick3DXrItem*>(&target))
193 return closestPointOnXrItem(*asXrItem, pos, margin);
194 if (auto *asModel =std::get_if<QQuick3DModel*>(&target))
195 return closestPointOnModel(view, *asModel, pos, margin);
196 return {};
197}
198
199static inline void sendTouch(QQuick3DXrView *view, const ClosestPointResult &point, int pointId, bool active)
200{
201 if (point.quickItem)
202 view->setTouchpoint(point.quickItem, point.point2D, pointId, active);
203}
204
205} // anonymous namespace
206
207
208// Sends appropriate touch events.
209// Updates the touchState and returns true if this target grabs the touch point.
210// touchState is input/output, and input contains the previous state if touchState->grabbed is true
211bool QQuick3DXrView::handleVirtualTouch(TouchTarget target, const QVector3D &pos, TouchState *touchState, QVector3D *offset)
212{
213 constexpr qreal sideMargin = 10;
214 constexpr qreal hoverHeight = 10;
215 constexpr qreal cancelDepth = 50;
216
217 constexpr qreal releaseHeight = 2; // Minimum vertical movement to count as a press/release when below
218 constexpr qreal releaseHeightSquared = releaseHeight * releaseHeight;
219 constexpr qreal smallDistance = 0.5; // Any movement shorter than this distance is insignificant
220 constexpr qreal sidewaysMax = 3; // Any horizontal movement larger than this distance means this is never going to be a press/release below the surface
221 constexpr qreal sidewaysMaxSquared = sidewaysMax * sidewaysMax;
222 constexpr qreal releaseSpeed = 5.0 / 1000; // Minimum vertical speed (cm/ms) to classify as a press/release
223 constexpr int interval = 100; // Time between checks to determine insignificant movement
224
225 const bool wasGrabbed = touchState->grabbed;
226 const bool wasPressed = touchState->pressed;
227 const qreal wasInside = wasGrabbed && touchState->touchDepth < 0.0;
228
229 const QVector3D oldScenePos = touchState->previous;
230 const QVector3D oldSceneNormal = touchState->normal;
231
232 // Note: We always grab the closest point on the surface. This means that for thin and flat models,
233 // we will snap to the backside, which is probably not what the user wants. If this turns out to
234 // be a problem when we evaluate this, we can change this algorithm to ignore points that have a
235 // normal vector pointing the wrong way.
236
237 auto closest = closestPointOnItem(this, target, pos, sideMargin);
238
239 bool grab = false;
240 bool pressed = false;
241
242 bool resetTimer = false;
243 float z = closest.distance; // assumes signed distance
244
245 if (wasGrabbed) {
246 // maintain grab as long as we don't move outside
247 grab = closest.grab && z > -cancelDepth && z < hoverHeight;
248
249 // if grab is false, we're definitely not pressed
250 if (grab) {
251 // We do a press/release transition if we
252 // 1. Moved between outside and inside
253 // OR
254 // 2. Had a fast enough vertical movement below the surface, without a horizontal movement
255
256 if (wasInside && closest.inside) {
257 // We stayed inside; let's see if we've had a strong vertical movement
258 int duration = touchState->timer.elapsed();
259 pressed = wasPressed; // We maintain pressed state unless proven otherwise
260 const QVector3D movement = pos - oldScenePos; // Movement is positive away from the surface
261 const qreal verticalDistance = QVector3D::dotProduct(movement, oldSceneNormal);
262 const qreal verticalDistanceSquared = verticalDistance * verticalDistance;
263 const qreal horizontalDistanceSquared = movement.lengthSquared() - verticalDistance*verticalDistance;
264
265 if (verticalDistanceSquared >= releaseHeightSquared && horizontalDistanceSquared < releaseHeightSquared) {
266 // We have moved a significant distance vertically, without moving horizontally,
267 // so this is a press/release (no-op if it's a press when already pressed)
268 pressed = verticalDistance < 0;
269 resetTimer = true;
270 } else if (duration >= interval) {
271 // Check to see if this can become a press/release in the future
272 if (horizontalDistanceSquared >= sidewaysMaxSquared) {
273 // This is sideways motion, and not a press/release
274 resetTimer = true;
275 } else if (pressed ? verticalDistance < smallDistance : verticalDistance > -smallDistance ) {
276 // we haven't had a significant vertical movement in the right direction (positive when pressed),
277 // so this is not the start of a press/release
278 resetTimer = true;
279 } else {
280 // If the speed is high enough, this can still become a press/release in the future
281 // (If we're pressed, we're looking for positive speed)
282 const qreal verticalSpeed = verticalDistance / duration;
283 const bool sufficientSpeed = pressed ? verticalSpeed > releaseSpeed : verticalSpeed < -releaseSpeed;
284 resetTimer = !sufficientSpeed;
285 }
286 }
287 } else {
288 // Either hover or move between outside and inside
289 pressed = closest.inside; // closest.inside is only true if z < 0, so we don't have to check for that
290 resetTimer = true;
291 }
292 }
293 } else {
294 // only grab if hovering, not if inside: we only want to register a press
295 // coming from a hover, not when approaching from below
296 grab = closest.grab && closest.distance > 0 && closest.distance < hoverHeight;
297 resetTimer = true;
298 }
299
300 if (grab) {
301 if (offset && z < 0)
302 *offset = closest.scenePosition - pos;
303 touchState->grabbed = true;
304 touchState->target = target;
305 touchState->touchDistance = qMax(z, 0.0); // Documented to be 0 when pressed
306 touchState->touchDepth = z;
307 touchState->pressed = pressed;
308 touchState->cursorPos = closest.point2D;
309 touchState->surfacePoint = closest.scenePosition;
310 touchState->normal = closest.sceneNormal;
311 touchState->uvPosition = closest.uvPosition;
312 if (resetTimer) {
313 touchState->previous = pos;
314 touchState->timer.start();
315 }
316
317 sendTouch(this, closest, touchState->pointId, pressed);
318 return true;
319 }
320
321 if (wasGrabbed) {
322 touchState->grabbed = false;
323 touchState->pressed = false;
324 touchState->target = {};
325 sendTouch(this, closest, touchState->pointId, false);
326 }
327
328 return false;
329}
330
331/*!
332 \qmlproperty bool QtQuick3D.Xr::XrView::passthroughEnabled
333 \summary Holds whether passthrough is enabled for the XR view.
334*/
335bool QQuick3DXrView::passthroughEnabled() const
336{
337 return m_xrManager.isPassthroughEnabled();
338}
339
340/*!
341 \qmlproperty QQuick3DXrRuntimeInfo QtQuick3D.Xr::XrView::runtimeInfo
342 \summary Provides information about the XR runtime for the XR view.
343*/
344
345QQuick3DXrRuntimeInfo *QQuick3DXrView::runtimeInfo() const
346{
347 return &m_xrRuntimeInfo;
348}
349
350void QQuick3DXrView::setEnvironment(QQuick3DSceneEnvironment *environment)
351{
352 QQuick3DViewport *view = m_xrManager.m_vrViewport;
353
354 // If the view is not created yet, we can't set the environment which means we need to
355 // set it again once the view is created...
356 if (!view) {
357 m_pendingSceneEnvironment = environment;
358 return;
359 }
360
361 auto oldEnvironment = view->environment();
362 if (oldEnvironment == environment)
363 return;
364
365 if (oldEnvironment)
366 disconnect(oldEnvironment);
367
368 view->setEnvironment(environment);
369
370 // The view will always have an environment, setting the environment to null will just mean the default environment
371 // is used. So querying the environment from the view is always valid (and we should do it here to make sure we're
372 // in sync with the view).
373 environment = view->environment();
374
375 handleClearColorChanged();
376 handleAAChanged();
377
378 connect(environment, &QQuick3DSceneEnvironment::backgroundModeChanged, this, &QQuick3DXrView::handleClearColorChanged);
379 connect(environment, &QQuick3DSceneEnvironment::clearColorChanged, this, &QQuick3DXrView::handleClearColorChanged);
380 connect(environment, &QQuick3DSceneEnvironment::antialiasingModeChanged, this, &QQuick3DXrView::handleAAChanged);
381 connect(environment, &QQuick3DSceneEnvironment::antialiasingQualityChanged, this, &QQuick3DXrView::handleAAChanged);
382
383 emit environmentChanged(environment);
384}
385
386/*!
387 \qmlproperty bool QtQuick3D.Xr::XrView::passthroughSupported
388 \summary Indicates whether passthrough is supported for the XR view.
389*/
390
391bool QQuick3DXrView::passthroughSupported() const
392{
393 if (!m_xrManager.isValid())
394 return false;
395
396 return m_xrManager.supportsPassthrough();
397}
398
399void QQuick3DXrView::setPassthroughEnabled(bool enable)
400{
401 if (!m_xrManager.isValid()) {
402 qWarning("Attempted to set passthrough mode without a valid XR manager");
403 return;
404 }
405
406 const bool orgPassthroughEnabled = m_xrManager.isPassthroughEnabled();
407 if (!m_xrManager.setPassthroughEnabled(enable)) {
408 if (enable)
409 qWarning("Enabling passthrough is not supported.");
410 else
411 qWarning("Disabling passthrough is not supported.");
412 return;
413 }
414
415 if (orgPassthroughEnabled != m_xrManager.isPassthroughEnabled())
416 emit passthroughEnabledChanged();
417}
418
419/*!
420 \qmlproperty enumeration QtQuick3D.Xr::XrView::fixedFoveation
421 \brief Controls the level of fixed foveated rendering for the XrView.
422 \default XrView.HighFoveation
423
424 Foveated rendering reduces GPU load by reducing image quality (resolution)
425 in areas where the difference is less perceptible to the eye. With fixed
426 foveated rendering, the areas with reduced visual fidelity are fixed and do
427 not change. On some platforms, there is no concept of fixed foveated
428 rendering or control over it. For example, VisionOS-based devices perform
429 dynamic, eye-tracked foveation; thus, the value of this property is
430 ignored in practice. Other devices, such as the Meta Quest 3, only
431 support fixed foveation, which makes this property relevant.
432
433 The value can be one of:
434 \value XrView.NoFoveation 0, no foveation.
435 \value XrView.LowFoveation 1, low foveation.
436 \value XrView.MediumFoveation 2, medium foveation.
437 \value XrView.HighFoveation 3, high foveation.
438
439 Where supported, the default is \c HighFoveation. Therefore, changing this
440 value in applications should be rarely needed in practice.
441*/
442
443QQuick3DXrView::FoveationLevel QQuick3DXrView::fixedFoveation() const
444{
445 return FoveationLevel(m_xrManager.getFixedFoveationLevel());
446}
447
448void QQuick3DXrView::setFixedFoveation(FoveationLevel level)
449{
450 const auto orgFoviationLevel = m_xrManager.getFixedFoveationLevel();
451 m_xrManager.setFixedFoveationLevel(QtQuick3DXr::FoveationLevel(level));
452 if (orgFoviationLevel != m_xrManager.getFixedFoveationLevel())
453 emit fixedFoveationChanged();
454}
455
456/*!
457 \qmlproperty bool QtQuick3D.Xr::XrView::isQuitOnSessionEndEnabled
458 \brief Holds whether the application should quit when the XR session ends.
459*/
460
461bool QQuick3DXrView::isQuitOnSessionEndEnabled() const
462{
463 return m_quitOnSessionEnd;
464}
465
466void QQuick3DXrView::setQuitOnSessionEnd(bool enable)
467{
468 if (m_quitOnSessionEnd == enable)
469 return;
470
471 m_quitOnSessionEnd = enable;
472 emit quitOnSessionEndChanged();
473}
474/*!
475 \qmlproperty RenderStats QtQuick3D.Xr::XrView::renderStats
476 \summary Holds rendering statistics for the XR view.
477*/
478
479QQuick3DRenderStats *QQuick3DXrView::renderStats() const
480{
481 return m_xrManager.m_vrViewport ? m_xrManager.m_vrViewport->renderStats() : nullptr;
482}
483
484void QQuick3DXrView::updateViewportGeometry()
485{
486 auto contentItem = m_xrManager.m_quickWindow->contentItem();
487 auto viewport = m_xrManager.m_vrViewport;
488 if (viewport->height() != contentItem->height())
489 viewport->setHeight(contentItem->height());
490 if (viewport->width() != contentItem->width())
491 viewport->setWidth(contentItem->width());
492 if (viewport->x() != contentItem->x())
493 viewport->setX(contentItem->x());
494 if (viewport->y() != contentItem->y())
495 viewport->setY(contentItem->y());
496}
497
498void QQuick3DXrView::handleSessionEnded()
499{
500 emit sessionEnded();
501 if (m_quitOnSessionEnd)
502 QCoreApplication::quit();
503}
504
505void QQuick3DXrView::handleClearColorChanged()
506{
507 auto env = environment();
508
509 if (env) {
510 if (env->backgroundMode() == QQuick3DSceneEnvironment::Color)
511 m_xrManager.m_quickWindow->setColor(env->clearColor());
512 else if (env->backgroundMode() == QQuick3DSceneEnvironment::Transparent)
513 m_xrManager.m_quickWindow->setColor(Qt::transparent);
514 }
515}
516
517void QQuick3DXrView::handleAAChanged()
518{
519 auto env = environment();
520 int samples = 1;
521 if (env && env->antialiasingMode() == QQuick3DSceneEnvironment::MSAA) {
522 switch (env->antialiasingQuality()) {
523 case QQuick3DSceneEnvironment::Medium:
524 samples = 2;
525 break;
526 case QQuick3DSceneEnvironment::High:
527 samples = 4;
528 break;
529 case QQuick3DSceneEnvironment::VeryHigh:
530 samples = 8;
531 break;
532 }
533 }
534 m_xrManager.setSamples(samples);
535}
536
537bool QQuick3DXrView::init()
538{
539 if (m_isInitialized) {
540 qWarning("Already initialized!");
541 return false;
542 }
543
544 connect(&m_xrManager, &QQuick3DXrManager::sessionEnded, this, &QQuick3DXrView::handleSessionEnded);
545 connect(&m_xrManager, &QQuick3DXrManager::frameReady, this, &QQuick3DXrView::frameReady);
546 connect(&m_xrManager, &QQuick3DXrManager::referenceSpaceChanged, this, &QQuick3DXrView::referenceSpaceChanged);
547 connect(&m_xrManager, &QQuick3DXrManager::multiViewRenderingEnabledChanged, this, &QQuick3DXrView::multiViewRenderingEnabledChanged);
548 connect(&m_xrManager, &QQuick3DXrManager::initialized, this, &QQuick3DXrView::init, Qt::UniqueConnection);
549
550 if (!m_xrManager.isReady() && !m_xrManager.initialize()) {
551 qCDebug(lcQuick3DXr, "Waiting for XR platform to be initialized");
552
553 return false;
554 }
555
556 if (!m_xrManager.initialize()) {
557 QString errorString = m_xrManager.errorString();
558 if (errorString.isEmpty())
559 errorString = tr("Failed to initialize XR platform");
560 qWarning("\n%s\n", qPrintable(errorString));
561 QMetaObject::invokeMethod(this, "initializeFailed", Qt::QueuedConnection, errorString);
562 return false;
563 }
564
565 // Create View3D
566 QSSG_CHECK_X(m_xrManager.m_vrViewport == nullptr, "View3D already created!");
567 auto viewport = new QQuick3DViewport(QQuick3DViewport::PrivateInstanceType::XrViewInstance);
568 viewport->setRenderMode(QQuick3DViewport::Underlay);
569 auto contentItem = m_xrManager.m_quickWindow->contentItem();
570 viewport->setParentItem(contentItem);
571 m_xrManager.m_vrViewport = viewport;
572 viewport->setImportScene(this);
573
574 contentItem->forceActiveFocus(Qt::MouseFocusReason);
575
576 connect(contentItem, &QQuickItem::heightChanged, this, &QQuick3DXrView::updateViewportGeometry);
577 connect(contentItem, &QQuickItem::widthChanged, this, &QQuick3DXrView::updateViewportGeometry);
578 connect(contentItem, &QQuickItem::xChanged, this, &QQuick3DXrView::updateViewportGeometry);
579 connect(contentItem, &QQuickItem::yChanged, this, &QQuick3DXrView::updateViewportGeometry);
580
581 QQuick3DSceneEnvironment *env = environment();
582 if (env) {
583 connect(env, &QQuick3DSceneEnvironment::backgroundModeChanged, this, &QQuick3DXrView::handleClearColorChanged);
584 connect(env, &QQuick3DSceneEnvironment::clearColorChanged, this, &QQuick3DXrView::handleClearColorChanged);
585 connect(env, &QQuick3DSceneEnvironment::antialiasingModeChanged, this, &QQuick3DXrView::handleAAChanged);
586 connect(env, &QQuick3DSceneEnvironment::antialiasingQualityChanged, this, &QQuick3DXrView::handleAAChanged);
587 }
588
589 // NOTE: If we've called async, we need to make sure the environment, etc. is set again
590 setEnvironment(m_pendingSceneEnvironment);
591 m_pendingSceneEnvironment = nullptr;
592
593 m_xrManager.update();
594
595 m_isInitialized = true;
596
597 return m_isInitialized;
598}
599
600/*!
601 \qmlmethod pickResult XrView::rayPick(vector3d origin, vector3d direction)
602
603 This method will \e shoot a ray into the scene starting at \a origin and in
604 \a direction and return information about the nearest intersection with an
605 object in the scene.
606
607 For example, pass the position and forward vector of
608 any object in a scene to see what object is in front of an item. This
609 makes it possible to do picking from any point in the scene.
610 */
611QQuick3DPickResult QQuick3DXrView::rayPick(const QVector3D &origin, const QVector3D &direction) const
612{
613 if (!m_xrManager.m_vrViewport)
614 return QQuick3DPickResult();
615
616 return m_xrManager.m_vrViewport->rayPick(origin, direction);
617}
618
619/*!
620 \qmlmethod List<pickResult> XrView::rayPickAll(vector3d origin, vector3d direction)
621
622 This method will \e shoot a ray into the scene starting at \a origin and in
623 \a direction and return a list of information about the nearest intersections with
624 objects in the scene.
625 The list is presorted by distance from the origin along the direction
626 vector with the nearest intersections appearing first and the furthest
627 appearing last.
628
629 This can, for instance, be called with the position and forward vector of
630 any object in a scene to see what objects are in front of an item. This
631 makes it possible to do picking from any point in the scene.
632 */
633QList<QQuick3DPickResult> QQuick3DXrView::rayPickAll(const QVector3D &origin, const QVector3D &direction) const
634{
635 if (!m_xrManager.m_vrViewport)
636 return {};
637
638 return m_xrManager.m_vrViewport->rayPickAll(origin, direction);
639}
640
641/*!
642 \qmlmethod pickResult XrView::rayPick(vector3d origin, vector3d direction, Model model)
643
644 This method will "shoot" a ray into the scene starting at \a origin and in
645 \a direction and return information about the intersection between the ray and the specified \a model.
646
647 \since 6.11
648*/
649QQuick3DPickResult QQuick3DXrView::rayPick(const QVector3D &origin, const QVector3D &direction, QQuick3DModel *model) const
650{
651 if (!m_xrManager.m_vrViewport)
652 return QQuick3DPickResult();
653
654 return m_xrManager.m_vrViewport->rayPick(origin, direction, model);
655}
656
657/*!
658 \qmlmethod pickResult XrView::closestPointPick(vector3d origin, float radius, Model model)
659
660 This method will find the point on the surface of \a model that is nearest to \a origin, within a distance of
661 \a radius. If \a model is \c null, the closest object within \a radius will be found.
662
663 If no such object exists, \c null is returned.
664
665 \since 6.11
666*/
667QQuick3DPickResult QQuick3DXrView::closestPointPick(const QVector3D &origin, float radius, QQuick3DModel *model) const
668{
669 if (!m_xrManager.m_vrViewport)
670 return QQuick3DPickResult();
671
672 return m_xrManager.m_vrViewport->closestPointPick(origin, radius, model);
673}
674
675/*!
676 \qmlmethod void XrView::setTouchpoint(Item target, point position, int pointId, bool pressed)
677
678 Sends a synthetic touch event to \a target, moving the touch point with ID \a pointId to \a position,
679 with \a pressed determining if the point is pressed.
680 Also sends the appropriate touch release event if \a pointId was previously active on a different
681 item.
682*/
683
684void QQuick3DXrView::setTouchpoint(QQuickItem *target, const QPointF &position, int pointId, bool pressed)
685{
686 view3d()->setTouchpoint(target, position, pointId, pressed);
687}
688
689// TODO: Maybe do a proper QQuick3DXrViewPrivate instead
690struct QQuick3DXrView::XrTouchStates
691{
692 QHash<int, TouchState> points;
693};
694
695/*!
696 \qmlmethod vector3d XrView::processTouch(vector3d position, int pointId)
697
698 This method will search for an XrItem, or a Model with a
699 \l{Texture::sourceItem}{sourceItem texture}, near \a position and send a
700 virtual touch event with touch point ID \a pointId if \a position maps to
701 a point on the surface.
702
703 The return value is the offset between \a position and the touched point on
704 the surface. This can be used to prevent a hand model from passing through
705 an XrItem.
706
707 \sa XrHandModel
708
709*/
710
711QVector3D QQuick3DXrView::processTouch(const QVector3D &pos, int pointId)
712{
713 QVector3D offset;
714
715 if (!m_touchStates)
716 m_touchStates = new XrTouchStates;
717 TouchState &state = m_touchStates->points[pointId];
718 state.pointId = pointId; // in case it's a new point that was default-constructed
719
720 bool grabbed = false;
721 const bool prevTarget = !std::holds_alternative<std::monostate>(state.target);
722 if (prevTarget) {
723 grabbed = handleVirtualTouch(state.target, pos, &state, &offset);
724 }
725 if (!grabbed) {
726 for (auto *item : std::as_const(m_xrItems)) {
727 // For simplicity, try handleVirtualTouch on target again, even if we know it's not going to be different
728 if (item->visible())
729 grabbed = handleVirtualTouch(item, pos, &state, &offset);
730 if (grabbed)
731 break;
732 }
733 }
734
735 if (!grabbed) {
736 constexpr float pickRadius = 10.0;
737 auto pickResult = closestPointPick(pos, pickRadius);
738 //qDebug() << "pick result" << pickResult.objectHit() << pickResult.position();
739 if (QQuick3DModel *obj = pickResult.objectHit())
740 grabbed = handleVirtualTouch(obj, pos, &state, &offset); // will call closestPointPick again, but this makes the code simpler
741 }
742
743 return offset;
744}
745
746/*!
747 \qmlmethod object XrView::touchpointState(int pointId)
748
749 This method returns the state of the touch point with ID \a pointId.
750 The state is represented by a map from property names to values:
751
752 \table
753 \header
754 \li Key
755 \li Type
756 \li Description
757 \row
758 \li \c grabbed
759 \li \c bool
760 \li Is the point grabbed by an item? If \c false, all other values are \c undefined.
761 \row
762 \li \c target
763 \li XrItem
764 \li The item that is grabbing the touch point, or \c null if there is no XrItem.
765 \row
766 \li \c pressed
767 \li \c bool
768 \li Is the touch point pressed?
769 \row
770 \li \c cursorPos
771 \li \c point
772 \li The 2D position of the touch point within \c target
773 \row
774 \li \c touchDistance
775 \li \c real
776 \li The distance from the plane to the touch point. It will be \c 0 if \c pressed is \c true.
777 \row
778 \li \c surfacePoint
779 \li \c vector3d
780 \li The position of the touch point in scene space. [since 6.11]
781 \row
782 \li \c normal
783 \li \c vector3d
784 \li The normal vector at the touch point in scene space. [since 6.11]
785 \row
786 \li \c uvPosition
787 \li \c vector2d
788 \li The UV position at the touch point. [since 6.11]
789 \row
790 \li \c model
791 \li \c Model
792 \li The model that is grabbing the touch point, or \c null if there is no Model. [since 6.11]
793 \endtable
794 */
795
796#define Q_TOUCHPOINT_STATE(prop) { QStringLiteral(#prop), QVariant::fromValue(it->prop) }
797QVariantMap QQuick3DXrView::touchpointState(int pointId) const
798{
799 auto constexpr end = QHash<int, TouchState>::const_iterator();
800 auto it = m_touchStates ? m_touchStates->points.constFind(pointId) : end;
801
802 if (it == end)
803 return { { QStringLiteral("grabbed"), QVariant::fromValue(false) } };
804 auto *itemPointer = std::get_if<QQuick3DXrItem *>(&it->target);
805 QQuick3DXrItem *xrItem = itemPointer ? *itemPointer : nullptr;
806 auto *modelPointer = std::get_if<QQuick3DModel *>(&it->target);
807 QQuick3DModel *model = modelPointer ? *modelPointer : nullptr;
808 return { { QStringLiteral("target"), QVariant::fromValue(xrItem) },
809 Q_TOUCHPOINT_STATE(grabbed),
810 Q_TOUCHPOINT_STATE(pressed),
811 Q_TOUCHPOINT_STATE(cursorPos),
812 Q_TOUCHPOINT_STATE(surfacePoint),
813 Q_TOUCHPOINT_STATE(normal),
814 Q_TOUCHPOINT_STATE(touchDistance),
815 Q_TOUCHPOINT_STATE(uvPosition),
816 { QStringLiteral("model"), QVariant::fromValue(model) }};
817}
818#undef Q_TOUCHPOINT_STATE
819
820/*!
821 \qmlproperty enumeration QtQuick3D.Xr::XrView::referenceSpace
822 \brief Gets or sets the reference space for the XR view.
823
824 It can be one of:
825 \value XrView.ReferenceSpaceUnknown
826 \value XrView.ReferenceSpaceLocal Origin is at the default view position (typically defined by a "reset view" operation).
827 \value XrView.ReferenceSpaceStage Origin is at floor height in the center of the user's defined area.
828 \value XrView.ReferenceSpaceLocalFloor Origin is at floor height, below the default view position.
829
830 \c ReferenceSpaceLocal is mainly useful for seated applications where the content is not positioned
831 relative to the floor, for example floating menus. The content will move when the user resets the view.
832
833 \c ReferenceSpaceStage is mainly useful for room-scale applications where the user will move freely within the
834 playing area. The content will not move when the user resets the view.
835
836 \c ReferenceSpaceLocalFloor is mainly useful for stationary applications (seated or standing) where the content is
837 positioned relative to the floor. The content will move when the user resets the view.
838
839 \note On visionOS, the reference space is always \c ReferenceSpaceLocalFloor and
840 cannot be changed. This means that applications designed with \c ReferenceSpaceLocal
841 will have their origin at floor level on visionOS, which can cause content to appear
842 at an unexpected position. To handle this, applications can check the
843 \l referenceSpace property at runtime and adjust the vertical position of their
844 content accordingly. For example:
845
846 \code
847 y: xrView.referenceSpace === XrView.ReferenceSpaceLocalFloor ? 130 : 0
848 \endcode
849
850 \default XrView.ReferenceSpaceLocal
851 \sa {Coordinate Systems and Units in \qxr}
852*/
853
854QQuick3DXrView::ReferenceSpace QQuick3DXrView::referenceSpace() const
855{
856 return ReferenceSpace(m_xrManager.getReferenceSpace());
857}
858
859void QQuick3DXrView::setReferenceSpace(ReferenceSpace newReferenceSpace)
860{
861 m_xrManager.setReferenceSpace(QtQuick3DXr::ReferenceSpace(newReferenceSpace));
862}
863
864bool QQuick3DXrView::depthSubmissionEnabled() const
865{
866 if (!m_xrManager.isValid()) {
867 qWarning("Attempted to check depth submission mode without a valid XR manager");
868 return false;
869 }
870
871 return m_xrManager.isDepthSubmissionEnabled();
872}
873
874/*!
875 \qmlproperty bool QtQuick3D.Xr::XrView::multiViewRenderingSupported
876
877 \brief This read-only property reports the availability of \l{Multiview Rendering}.
878
879 \sa multiViewRenderingEnabled
880 */
881bool QQuick3DXrView::isMultiViewRenderingSupported() const
882{
883 if (!m_xrManager.isValid())
884 return false;
885
886 return m_xrManager.isMultiViewRenderingSupported();
887}
888
889/*!
890 \qmlproperty bool QtQuick3D.Xr::XrView::multiViewRenderingEnabled
891
892 \brief This is a read-only property that reports if \l{Multiview Rendering} is enabled for the XR view.
893
894 \default true
895
896 This property tells you if multiview rendering is actually in use at run time.
897 When not supported, the value will flip back to \c false.
898
899 Enabling multiview rendering is recommended. It can improve performance and reduce
900 CPU and GPU power usage. It defaults to disabled to ensure maximum
901 compatibility. Developers are encouraged to verify that their application
902 renders as expected with multiViewRenderingEnabled set to \c true and then
903 leave it set afterward.
904
905 \note Certain Qt Quick and Quick 3D features that involve shader code that is
906 provided by the application may need this code to be modified to be multiview
907 compatible. Examples of these are custom 2D and 3D materials and
908 postprocessing effects. The \l {Multiview Rendering} documentation provides
909 more information on this and how to disable multiview rendering.
910
911 \sa multiViewRenderingSupported {Multiview Rendering}
912*/
913bool QQuick3DXrView::multiViewRenderingEnabled() const
914{
915 if (!m_xrManager.isValid())
916 return false;
917
918 return m_xrManager.isMultiViewRenderingEnabled();
919}
920
921void QQuick3DXrView::registerXrItem(QQuick3DXrItem *newXrItem)
922{
923 m_xrItems.append(newXrItem);
924}
925
926void QQuick3DXrView::unregisterXrItem(QQuick3DXrItem *xrItem)
927{
928 m_xrItems.removeAll(xrItem);
929}
930
931/*!
932 \qmlproperty bool QtQuick3D.Xr::XrView::depthSubmissionEnabled
933 \brief Controls whether submitting the depth buffer to the XR compositor
934 is enabled.
935 \default false
936
937 By default, the depth buffer used by the 3D scene in the XrView is not exposed
938 to the XR compositor. However, in some platforms, depth submission is implicit
939 and cannot be disabled or controlled by the application. An example of this is
940 VisionOS. Changing this property has no effect on those platforms. Elsewhere,
941 with OpenXR in particular, support depends on the OpenXR implementation used
942 at run time.
943
944 It is always safe to set depthSubmissionEnabled to \c true. It will just have
945 no effect when not supported by the underlying stack. To be sure, you can
946 inspect the debug output to see if depth submission is in use.
947 Submitting the depth buffer may improve reprojections that the XR compositor
948 may perform. Reprojection could happen, for example, when the system cannot
949 maintain the target frame rate and thus has to resort to predicting frame
950 contents to improve and stabilize the user's perception of the
951 scene and reduce possible motion sickness. However, the application and
952 Qt have no control over data usage. It could also happen that
953 submitting depth data has no practical effects and is ignored by the
954 underlying XR runtime and compositor.
955
956 In practice, submitting the depth buffer implies rendering into a depth
957 texture provided by the XR runtime instead of the intermediate texture/render buffer
958 created and managed by Qt. Rendering into a depth texture has certain lower-level
959 consequences that can have a performance impact:
960
961 When using \l{QtQuick3D::SceneEnvironment::antialiasingMode}{multisample antialiasing}
962 (MSAA), enabling depth submission implies rendering into a multisample depth
963 texture and resolving the samples into the non-multisample depth texture provided by
964 the XR runtime. Without depth submission,
965 the resolve step would not be necessary. In addition, some 3D APIs
966 do not support resolving multisample depth-stencil data (see
967 the \l{QRhi::ResolveDepthStencil} flag for details). Without this support,
968 attempts to enable depth submission in combination with MSAA are gracefully ignored.
969
970 Even when MSAA is not used, enabling depth submission triggers writing out
971 depth data with 3D APIs that have control over this. The store operation for
972 depth/stencil data is typically indicated by Qt as unnecessary, which can
973 have positive performance impacts on tiled GPU architectures. This is not
974 done with depth submission because depth data must always be written out
975 from Qt's perspective.
976
977 \note We recommended that developers test their applications with depth
978 submission enabled, evaluate the advantages and disadvantages, and make a
979 conscious choice based on their testing if they wish to enable it or not.
980*/
981
982void QQuick3DXrView::setDepthSubmissionEnabled(bool enable)
983{
984 if (!m_xrManager.isValid()) {
985 qWarning("Attempted to set depth submission mode without a valid XR manager");
986 return;
987 }
988
989 const bool orgDepthSubmission = m_xrManager.isDepthSubmissionEnabled();
990
991 m_xrManager.setDepthSubmissionEnabled(enable);
992
993 if (orgDepthSubmission != m_xrManager.isDepthSubmissionEnabled())
994 emit depthSubmissionEnabledChanged();
995}
996
997void QQuick3DXrView::setXROrigin(QQuick3DXrOrigin *newXrOrigin)
998{
999 if (m_xrOrigin == newXrOrigin)
1000 return;
1001
1002 QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DXrView::setXROrigin, newXrOrigin, m_xrOrigin);
1003
1004 m_xrOrigin = newXrOrigin;
1005
1006 // Make sure the XrOrigin has a parent item, if it hasn't, we're it.
1007 if (m_xrOrigin && !m_xrOrigin->parentItem())
1008 m_xrOrigin->setParentItem(this);
1009
1010 m_xrManager.setXROrigin(m_xrOrigin);
1011
1012 emit xrOriginChanged();
1013}
1014
1015QQuick3DViewport *QQuick3DXrViewPrivate::getView3d(QQuick3DXrView *view)
1016{
1017 QSSG_ASSERT(view != nullptr, return nullptr);
1018 return view->view3d();
1019}
1020
1021/*!
1022 \qmlsignal XrView::initializeFailed(const QString &errorString)
1023
1024 Emitted when initialization fails, and there is a new \a errorString
1025 describing the failure.
1026 */
1027
1028/*!
1029 \qmlsignal XrView::sessionEnded()
1030
1031 Emitted when the session ends.
1032 */
1033
1034/*!
1035 \qmlsignal XrView::frameReady()
1036 \internal
1037
1038 Emitted when a new frame is ready.
1039 */
1040
1041QT_END_NAMESPACE
Combined button and popup list for selecting options.
#define Q_TOUCHPOINT_STATE(prop)
\qmlmethod object XrView::touchpointState(int pointId)