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