1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dxrcamera_p.h"
5#include "qquick3dxrorigin_p.h"
6#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
7#include <QtQuick3D/private/qquick3dutils_p.h>
8#include <QtQuick3D/private/qquick3dnode_p_p.h>
9
10#include <cmath>
11
12QT_BEGIN_NAMESPACE
13/*!
14 \qmltype XrCamera
15 \inherits Node
16 \inqmlmodule QtQuick3D.Xr
17 \brief Tracks spatial position and orientation from which the user views an XR scene.
18
19 The XrCamera is a tracked spatial node that tracks the spatial position and orientation
20 of the users's view of an XR scene.
21
22 Since this is a tracked node, its spatial properties should be considered read-only.
23 Properties specific to the XrCamera, such as the \l{clipNear}{near} and \l{clipFar}{far} clip planes, are settable.
24 When set, they override preferred values.
25
26
27 \sa XrOrigin::camera
28*/
29
30QQuick3DXrEyeCamera::QQuick3DXrEyeCamera(QQuick3DXrOrigin *parent)
31 : QQuick3DCamera(*(new QQuick3DNodePrivate(QQuick3DNodePrivate::Type::CustomCamera)), parent)
32{
33}
34
35float QQuick3DXrEyeCamera::leftTangent() const
36{
37 return m_leftTangent;
38}
39
40float QQuick3DXrEyeCamera::rightTangent() const
41{
42 return m_rightTangent;
43}
44
45float QQuick3DXrEyeCamera::upTangent() const
46{
47 return m_upTangent;
48}
49
50float QQuick3DXrEyeCamera::downTangent() const
51{
52 return m_downTangent;
53}
54
55float QQuick3DXrEyeCamera::clipNear() const
56{
57 return m_clipNear;
58}
59
60float QQuick3DXrEyeCamera::clipFar() const
61{
62 return m_clipFar;
63}
64
65void QQuick3DXrEyeCamera::setLeftTangent(float leftTangent)
66{
67 if (qFuzzyCompare(p1: m_leftTangent, p2: leftTangent))
68 return;
69
70 m_leftTangent = leftTangent;
71 emit leftTangentChanged(leftTangent: m_leftTangent);
72 markDirty(flag: DirtyFlag::ProjectionDirty);
73}
74
75void QQuick3DXrEyeCamera::setRightTangent(float rightTangent)
76{
77 if (qFuzzyCompare(p1: m_rightTangent, p2: rightTangent))
78 return;
79
80 m_rightTangent = rightTangent;
81 emit rightTangentChanged(rightTangent: m_rightTangent);
82 markDirty(flag: DirtyFlag::ProjectionDirty);
83}
84
85void QQuick3DXrEyeCamera::setUpTangent(float upTangent)
86{
87 if (qFuzzyCompare(p1: m_upTangent, p2: upTangent))
88 return;
89
90 m_upTangent = upTangent;
91 emit upTangentChanged(upTangent: m_upTangent);
92 markDirty(flag: DirtyFlag::ProjectionDirty);
93}
94
95void QQuick3DXrEyeCamera::setDownTangent(float downTangent)
96{
97 if (qFuzzyCompare(p1: m_downTangent, p2: downTangent))
98 return;
99
100 m_downTangent = downTangent;
101 emit downTangentChanged(downTangent: m_downTangent);
102 markDirty(flag: DirtyFlag::ProjectionDirty);
103}
104
105void QQuick3DXrEyeCamera::setClipNear(float clipNear)
106{
107 if (qFuzzyCompare(p1: m_clipNear, p2: clipNear))
108 return;
109
110 m_clipNear = clipNear;
111 emit clipNearChanged(clipNear: m_clipNear);
112 markDirty(flag: DirtyFlag::ProjectionDirty);
113}
114
115void QQuick3DXrEyeCamera::setClipFar(float clipFar)
116{
117 if (qFuzzyCompare(p1: m_clipFar, p2: clipFar))
118 return;
119
120 m_clipFar = clipFar;
121 emit clipFarChanged(clipFar: m_clipFar);
122 markDirty(flag: DirtyFlag::ProjectionDirty);
123}
124
125void QQuick3DXrEyeCamera::setProjection(const QMatrix4x4 &projection)
126{
127 m_projection = projection;
128 markDirty(flag: DirtyFlag::ProjectionChanged);
129}
130
131QSSGRenderGraphObject *QQuick3DXrEyeCamera::updateSpatialNode(QSSGRenderGraphObject *node)
132{
133 QSSGRenderCamera *camera = static_cast<QSSGRenderCamera *>(QQuick3DCamera::updateSpatialNode(node));
134 if (camera) {
135 // Projection changed takes precedence over projection dirty
136 bool changed = false;
137 if (m_dirtyFlags & DirtyFlag::ProjectionChanged) {
138 camera->projection = m_projection;
139 qUpdateIfNeeded(orig&: camera->clipNear, updated: m_clipNear);
140 qUpdateIfNeeded(orig&: camera->clipFar, updated: m_clipFar);
141 m_dirtyFlags &= ~DirtyFlag::ProjectionChanged;
142 changed = true;
143 } else if (m_dirtyFlags & DirtyFlag::ProjectionDirty) {
144 maybeUpdateProjection();
145 changed |= qUpdateIfNeeded(orig&: camera->projection, updated: m_projection);
146 changed |= qUpdateIfNeeded(orig&: camera->clipNear, updated: m_clipNear);
147 changed |= qUpdateIfNeeded(orig&: camera->clipFar, updated: m_clipFar);
148 m_dirtyFlags &= ~DirtyFlag::ProjectionDirty;
149 }
150
151 if (m_dirtyFlags & DirtyFlag::ClipChanged) {
152 changed |= qUpdateIfNeeded(orig&: camera->clipNear, updated: m_clipNear);
153 changed |= qUpdateIfNeeded(orig&: camera->clipFar, updated: m_clipFar);
154 m_dirtyFlags &= ~DirtyFlag::ClipChanged;
155 }
156
157 // Reset the dirty flag after all updates have been done
158 m_dirtyFlags = 0;
159
160 if (changed)
161 camera->markDirty(dirtyFlag: QSSGRenderCamera::DirtyFlag::CameraDirty);
162 }
163 return camera;
164}
165
166void QQuick3DXrEyeCamera::markDirty(DirtyFlag flag)
167{
168 if (m_dirtyFlags & flag)
169 return;
170
171 m_dirtyFlags |= flag;
172 update();
173}
174
175void QQuick3DXrEyeCamera::maybeUpdateProjection()
176{
177 QSSG_ASSERT(m_dirtyFlags & DirtyFlag::ProjectionDirty, return);
178
179 const float right = m_rightTangent * m_clipNear;
180 const float top = m_upTangent * m_clipNear;
181#if defined(Q_OS_VISIONOS)
182 // cp_view_get_tangents always returns positive values
183 // so we need to negate the left and down tangents
184 const float left = -m_leftTangent * m_clipNear;
185 const float bottom = -m_downTangent * m_clipNear;
186#else
187 const float left = m_leftTangent * m_clipNear;
188 const float bottom = m_downTangent * m_clipNear;
189#endif
190
191 float *m = m_projection.data();
192
193 m[0] = 2 * m_clipNear / (right - left);
194 m[4] = 0;
195 m[8] = (right + left) / (right - left);
196 m[12] = 0;
197
198 m[1] = 0;
199 m[5] = 2 * m_clipNear / (top - bottom);
200 m[9] = (top + bottom) / (top - bottom);
201 m[13] = 0;
202
203 m[2] = 0;
204 m[6] = 0;
205 m[10] = m_clipFar / (m_clipNear - m_clipFar);
206 m[14] = m_clipFar * m_clipNear / (m_clipNear - m_clipFar);
207
208
209 m[3] = 0;
210 m[7] = 0;
211 m[11] = -1;
212 m[15] = 0;
213
214 const bool isReverseZ = false; // placeholder
215 if (isReverseZ) {
216 if (std::isinf(x: m_clipFar)) {
217 m[10] = 0;
218 m[14] = m_clipNear;
219 } else {
220 m[10] = -m[10] - 1;
221 m[14] = -m[14];
222 }
223 } else if (std::isinf(x: m_clipFar)) {
224 m[10] = -1;
225 m[14] = -m_clipNear;
226 }
227}
228
229QQuick3DXrCamera::QQuick3DXrCamera(QQuick3DXrOrigin *parent)
230 : QQuick3DNode(parent)
231{
232}
233
234QQuick3DXrCamera::~QQuick3DXrCamera()
235{
236}
237
238/*!
239 \qmlproperty float QtQuick3D.Xr::XrCamera::clipNear
240 \brief The start of the distance range, with reference to the camera position, in which objects will appear.
241
242 \note Unless set explicitly, the clipNear value will be set to the device's preferred value.
243*/
244
245float QQuick3DXrCamera::clipNear() const
246{
247 return m_clipNear;
248}
249
250/*!
251 \qmlproperty float QtQuick3D.Xr::XrCamera::clipFar
252 \brief The end of the distance range, with reference to the camera position, in which objects will appear.
253
254 \note Unless set explicitly, the clipFar value will be set to the device's preferred value.
255*/
256
257float QQuick3DXrCamera::clipFar() const
258{
259 return m_clipFar;
260}
261
262void QQuick3DXrCamera::setClipNear(float clipNear)
263{
264 if (qFuzzyCompare(p1: m_clipNear, p2: clipNear))
265 return;
266
267 m_clipNear = clipNear;
268
269 syncCameraSettings();
270
271 emit clipNearChanged(clipNear: m_clipNear);
272}
273
274void QQuick3DXrCamera::setClipFar(float clipFar)
275{
276 if (qFuzzyCompare(p1: m_clipFar, p2: clipFar))
277 return;
278
279 m_clipFar = clipFar;
280
281 syncCameraSettings();
282
283 emit clipFarChanged(clipFar: m_clipFar);
284}
285
286void QQuick3DXrCamera::itemChange(ItemChange change, const ItemChangeData &data)
287{
288 // Sanity check (If we get a tracked item we'll do this there instead).
289 if (change == QQuick3DObject::ItemParentHasChanged) {
290 if (data.item != nullptr) {
291 if (QQuick3DXrOrigin *xrOrigin = qobject_cast<QQuick3DXrOrigin *>(object: data.item)) {
292 xrOrigin->setCamera(this);
293 } else {
294 qWarning() << "XrCamera must be a child of an XrOrigin!";
295 setParentItem(nullptr);
296 }
297 } else {
298 QQuick3DNode::itemChange(change, data);
299 }
300 }
301}
302
303void QQuick3DXrCamera::syncCameraSettings()
304{
305 QQuick3DXrOrigin *xrOrigin = qobject_cast<QQuick3DXrOrigin *>(object: parentItem());
306 if (xrOrigin && xrOrigin->camera() == this)
307 xrOrigin->syncCameraSettings();
308}
309
310QT_END_NAMESPACE
311

source code of qtquick3d/src/xr/quick3dxr/qquick3dxrcamera.cpp