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 | |
12 | QT_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 | |
30 | QQuick3DXrEyeCamera::QQuick3DXrEyeCamera(QQuick3DXrOrigin *parent) |
31 | : QQuick3DCamera(*(new QQuick3DNodePrivate(QQuick3DNodePrivate::Type::CustomCamera)), parent) |
32 | { |
33 | } |
34 | |
35 | float QQuick3DXrEyeCamera::leftTangent() const |
36 | { |
37 | return m_leftTangent; |
38 | } |
39 | |
40 | float QQuick3DXrEyeCamera::rightTangent() const |
41 | { |
42 | return m_rightTangent; |
43 | } |
44 | |
45 | float QQuick3DXrEyeCamera::upTangent() const |
46 | { |
47 | return m_upTangent; |
48 | } |
49 | |
50 | float QQuick3DXrEyeCamera::downTangent() const |
51 | { |
52 | return m_downTangent; |
53 | } |
54 | |
55 | float QQuick3DXrEyeCamera::clipNear() const |
56 | { |
57 | return m_clipNear; |
58 | } |
59 | |
60 | float QQuick3DXrEyeCamera::clipFar() const |
61 | { |
62 | return m_clipFar; |
63 | } |
64 | |
65 | void 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 | |
75 | void 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 | |
85 | void 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 | |
95 | void 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 | |
105 | void 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 | |
115 | void 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 | |
125 | void QQuick3DXrEyeCamera::setProjection(const QMatrix4x4 &projection) |
126 | { |
127 | m_projection = projection; |
128 | markDirty(flag: DirtyFlag::ProjectionChanged); |
129 | } |
130 | |
131 | QSSGRenderGraphObject *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 | |
166 | void QQuick3DXrEyeCamera::markDirty(DirtyFlag flag) |
167 | { |
168 | if (m_dirtyFlags & flag) |
169 | return; |
170 | |
171 | m_dirtyFlags |= flag; |
172 | update(); |
173 | } |
174 | |
175 | void 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 | |
229 | QQuick3DXrCamera::QQuick3DXrCamera(QQuick3DXrOrigin *parent) |
230 | : QQuick3DNode(parent) |
231 | { |
232 | } |
233 | |
234 | QQuick3DXrCamera::~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 | |
245 | float 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 | |
257 | float QQuick3DXrCamera::clipFar() const |
258 | { |
259 | return m_clipFar; |
260 | } |
261 | |
262 | void 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 | |
274 | void 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 | |
286 | void 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 | |
303 | void QQuick3DXrCamera::syncCameraSettings() |
304 | { |
305 | QQuick3DXrOrigin *xrOrigin = qobject_cast<QQuick3DXrOrigin *>(object: parentItem()); |
306 | if (xrOrigin && xrOrigin->camera() == this) |
307 | xrOrigin->syncCameraSettings(); |
308 | } |
309 | |
310 | QT_END_NAMESPACE |
311 | |