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