1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "../qquick3dxrinputmanager_p.h"
5#include "qopenxrinputmanager_p.h"
6#include "openxr/qopenxrhelpers_p.h"
7#include "qquick3dxrhandinput_p.h"
8#include "qquick3dxrhandmodel_p.h"
9
10#include "qquick3dxrcontroller_p.h" //### InputAction enum
11
12#include <QDebug>
13
14#include <private/qquick3djoint_p.h>
15
16QT_BEGIN_NAMESPACE
17
18Q_DECLARE_LOGGING_CATEGORY(lcQuick3DXr);
19
20QQuick3DXrInputManagerPrivate::QQuick3DXrInputManagerPrivate(QQuick3DXrInputManager &manager)
21 : q_ptr(&manager)
22{
23 m_handInputState[Hand::LeftHand] = new QQuick3DXrHandInput(this);
24 m_handInputState[Hand::RightHand] = new QQuick3DXrHandInput(this);
25}
26
27QQuick3DXrInputManagerPrivate::~QQuick3DXrInputManagerPrivate()
28{
29 teardown();
30 delete m_handInputState[Hand::LeftHand];
31 delete m_handInputState[Hand::RightHand];
32
33 m_handInputState[Hand::LeftHand] = nullptr;
34 m_handInputState[Hand::RightHand] = nullptr;
35}
36
37QQuick3DXrInputManagerPrivate::QXRHandComponentPath QQuick3DXrInputManagerPrivate::makeHandInputPaths(const QByteArrayView path)
38{
39 QXRHandComponentPath res;
40 setPath(path&: res.paths[Hand::LeftHand], pathString: "/user/hand/left/" + path);
41 setPath(path&: res.paths[Hand::RightHand], pathString: "/user/hand/right/" + path);
42 return res;
43}
44
45
46XrPath QQuick3DXrInputManagerPrivate::makeInputPath(const QByteArrayView path)
47{
48 XrPath res;
49 setPath(path&: res, pathString: path.toByteArray());
50 return res;
51}
52
53QQuick3DGeometry *QQuick3DXrInputManagerPrivate::createHandMeshGeometry(const HandMeshData &handMeshData)
54{
55 QQuick3DGeometry *geometry = new QQuick3DGeometry();
56 geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
57
58 // Figure out which attributes should be used
59 const qsizetype expectedLength = handMeshData.vertexPositions.size();
60 bool hasPositions = !handMeshData.vertexPositions.isEmpty();
61 bool hasNormals = handMeshData.vertexNormals.size() >= expectedLength;
62 bool hasUV0s = handMeshData.vertexUVs.size() >= expectedLength;
63 bool hasJoints = handMeshData.vertexBlendIndices.size() >= expectedLength;
64 bool hasWeights = handMeshData.vertexBlendWeights.size() >= expectedLength;
65 bool hasIndexes = !handMeshData.indices.isEmpty();
66
67 int offset = 0;
68 if (hasPositions) {
69 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic, offset, componentType: QQuick3DGeometry::Attribute::ComponentType::F32Type);
70 offset += 3 * sizeof(float);
71 }
72
73 if (hasNormals) {
74 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::NormalSemantic, offset, componentType: QQuick3DGeometry::Attribute::ComponentType::F32Type);
75 offset += 3 * sizeof(float);
76 }
77
78 if (hasUV0s) {
79 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoordSemantic, offset, componentType: QQuick3DGeometry::Attribute::ComponentType::F32Type);
80 offset += 2 * sizeof(float);
81 }
82
83 if (hasJoints) {
84 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::JointSemantic, offset, componentType: QQuick3DGeometry::Attribute::ComponentType::I32Type);
85 offset += 4 * sizeof(qint32);
86 }
87
88 if (hasWeights) {
89 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::WeightSemantic, offset, componentType: QQuick3DGeometry::Attribute::ComponentType::F32Type);
90 offset += 4 * sizeof(float);
91 }
92
93 if (hasIndexes)
94 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic, offset: 0, componentType: QQuick3DGeometry::Attribute::ComponentType::U16Type);
95
96 // set up the vertex buffer
97 const int stride = offset;
98 const qsizetype bufferSize = expectedLength * stride;
99 geometry->setStride(stride);
100
101 QByteArray vertexBuffer;
102 vertexBuffer.reserve(asize: bufferSize);
103
104 QVector3D minBounds;
105 QVector3D maxBounds;
106
107 auto appendFloat = [&vertexBuffer](float f) {
108 vertexBuffer.append(s: reinterpret_cast<const char *>(&f), len: sizeof(float));
109 };
110 auto appendInt = [&vertexBuffer](qint32 i) {
111 vertexBuffer.append(s: reinterpret_cast<const char *>(&i), len: sizeof(qint32));
112 };
113
114 for (qsizetype i = 0; i < expectedLength; ++i) {
115 // start writing float values to vertexBuffer
116 if (hasPositions) {
117 const QVector3D position = OpenXRHelpers::toQVector(v: handMeshData.vertexPositions[i]);
118 appendFloat(position.x());
119 appendFloat(position.y());
120 appendFloat(position.z());
121 minBounds.setX(qMin(a: minBounds.x(), b: position.x()));
122 maxBounds.setX(qMax(a: maxBounds.x(), b: position.x()));
123 minBounds.setY(qMin(a: minBounds.y(), b: position.y()));
124 maxBounds.setY(qMax(a: maxBounds.y(), b: position.y()));
125 minBounds.setZ(qMin(a: minBounds.z(), b: position.z()));
126 maxBounds.setZ(qMax(a: maxBounds.z(), b: position.z()));
127 }
128 if (hasNormals) {
129 const auto &normal = handMeshData.vertexNormals[i];
130 appendFloat(normal.x);
131 appendFloat(normal.y);
132 appendFloat(normal.z);
133 }
134
135 if (hasUV0s) {
136 const auto &uv0 = handMeshData.vertexUVs[i];
137 appendFloat(uv0.x);
138 appendFloat(uv0.y);
139 }
140
141 if (hasJoints) {
142 const auto &joint = handMeshData.vertexBlendIndices[i];
143 appendInt(joint.x);
144 appendInt(joint.y);
145 appendInt(joint.z);
146 appendInt(joint.w);
147 }
148
149 if (hasWeights) {
150 const auto &weight = handMeshData.vertexBlendWeights[i];
151 appendFloat(weight.x);
152 appendFloat(weight.y);
153 appendFloat(weight.z);
154 appendFloat(weight.w);
155 }
156 }
157
158 geometry->setBounds(min: minBounds, max: maxBounds);
159 geometry->setVertexData(vertexBuffer);
160
161 // Index Buffer
162 if (hasIndexes) {
163 const qsizetype indexLength = handMeshData.indices.size();
164 QByteArray indexBuffer;
165 indexBuffer.reserve(asize: indexLength * sizeof(int16_t));
166 for (qsizetype i = 0; i < indexLength; ++i) {
167 const auto &index = handMeshData.indices[i];
168 indexBuffer.append(s: reinterpret_cast<const char *>(&index), len: sizeof(int16_t));
169 }
170 geometry->setIndexData(indexBuffer);
171 }
172
173 return geometry;
174}
175
176void QQuick3DXrInputManagerPrivate::init(XrInstance instance, XrSession session)
177{
178 if (m_initialized) {
179 qWarning() << "QQuick3DXrInputManager: Trying to initialize an already initialized session";
180 teardown();
181 }
182
183 m_instance = instance;
184 m_session = session;
185
186 setupHandTracking();
187
188 setupActions();
189
190 QXRHandComponentPath aClick = makeHandInputPaths(path: "input/a/click"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
191 QXRHandComponentPath bClick = makeHandInputPaths(path: "input/b/click"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
192 QXRHandComponentPath aTouch = makeHandInputPaths(path: "input/a/touch"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
193 QXRHandComponentPath bTouch = makeHandInputPaths(path: "input/b/touch"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
194
195 QXRHandComponentPath xClick = makeHandInputPaths(path: "input/x/click"); // OCULUS_TOUCH (left)
196 QXRHandComponentPath yClick = makeHandInputPaths(path: "input/y/click"); // OCULUS_TOUCH (left)
197 QXRHandComponentPath xTouch = makeHandInputPaths(path: "input/x/touch"); // OCULUS_TOUCH (left)
198 QXRHandComponentPath yTouch = makeHandInputPaths(path: "input/y/touch"); // OCULUS_TOUCH (left)
199
200 QXRHandComponentPath menuClick = makeHandInputPaths(path: "input/menu/click"); // OCULUS_TOUCH (left) | MICROSOFT_MRM (right + left) | HTC_VIVE (right + left)
201 QXRHandComponentPath systemClick = makeHandInputPaths(path: "input/system/click"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left) | HTC_VIVE (right + left)
202 QXRHandComponentPath systemTouch = makeHandInputPaths(path: "input/system/touch"); // VALVE_INDEX (right + left)
203
204 QXRHandComponentPath squeezeValue = makeHandInputPaths(path: "input/squeeze/value"); // right + left: OCULUS_TOUCH | VALVE_INDEX
205 QXRHandComponentPath squeezeForce = makeHandInputPaths(path: "input/squeeze/force"); // right + left: VALVE_INDEX
206 QXRHandComponentPath squeezeClick = makeHandInputPaths(path: "input/squeeze/click"); // right + left: MICROSOFT_MRM | HTC_VIVE
207
208 QXRHandComponentPath triggerValue = makeHandInputPaths(path: "input/trigger/value"); // right + left: OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
209 QXRHandComponentPath triggerTouch = makeHandInputPaths(path: "input/trigger/touch"); // right + left: OCULUS_TOUCH | VALVE_INDEX
210 QXRHandComponentPath triggerClick = makeHandInputPaths(path: "input/trigger/click"); // right + left: VALVE_INDEX | HTC_VIVE
211
212 QXRHandComponentPath thumbstickX = makeHandInputPaths(path: "input/thumbstick/x"); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
213 QXRHandComponentPath thumbstickY = makeHandInputPaths(path: "input/thumbstick/y"); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
214 QXRHandComponentPath thumbstickClick = makeHandInputPaths(path: "input/thumbstick/click"); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
215 QXRHandComponentPath thumbstickTouch = makeHandInputPaths(path: "input/thumbstick/touch"); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left)
216 QXRHandComponentPath thumbrestTouch = makeHandInputPaths(path: "input/thumbrest/touch"); // OCULUS_TOUCH (right + left)
217
218 QXRHandComponentPath trackpadX = makeHandInputPaths(path: "input/trackpad/x"); // right + left: VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
219 QXRHandComponentPath trackpadY = makeHandInputPaths(path: "input/trackpad/y"); // right + left: VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
220 QXRHandComponentPath trackpadForce = makeHandInputPaths(path: "input/trackpad/force"); // right + left: VALVE_INDEX
221 QXRHandComponentPath trackpadClick = makeHandInputPaths(path: "input/trackpad/click"); // right + left: VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
222 QXRHandComponentPath trackpadTouch = makeHandInputPaths(path: "input/trackpad/touch"); // right + left: MICROSOFT_MRM | HTC_VIVE
223
224 XrPath handLeftGripPose; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
225 XrPath handLeftAimPose; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
226 XrPath handLeftHaptic; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
227
228 XrPath handRightGripPose; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
229 XrPath handRightAimPose; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
230 XrPath handRightHaptic; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
231
232 // Hand Left
233
234 setPath(path&: handLeftGripPose, pathString: "/user/hand/left/input/grip/pose");
235 setPath(path&: handLeftAimPose, pathString: "/user/hand/left/input/aim/pose");
236 setPath(path&: handLeftHaptic, pathString: "/user/hand/left/output/haptic");
237
238 setPath(path&: handRightGripPose, pathString: "/user/hand/right/input/grip/pose");
239 setPath(path&: handRightAimPose, pathString: "/user/hand/right/input/aim/pose");
240 setPath(path&: handRightHaptic, pathString: "/user/hand/right/output/haptic");
241
242 // Bindings
243
244 using XrActionBindings = std::vector<XrActionSuggestedBinding>;
245 using HandInputMapping = std::vector<std::tuple<QQuick3DXrInputAction::Action, QXRHandComponentPath, SubPathSelector>>;
246 auto addToBindings = [this](XrActionBindings &bindings, const HandInputMapping &defs){
247 for (const auto &[actionId, path, selector] : defs) {
248 if (selector & LeftHandSubPath)
249 bindings.push_back(x: { .action: m_inputActions[actionId], .binding: path.paths[Hand::LeftHand] });
250 if (selector & RightHandSubPath)
251 bindings.push_back(x: { .action: m_inputActions[actionId], .binding: path.paths[Hand::RightHand] });
252 }
253 };
254
255 // Oculus Touch
256 {
257 HandInputMapping mappingDefs {
258 { QQuick3DXrInputAction::Button1Pressed, xClick, LeftHandSubPath },
259 { QQuick3DXrInputAction::Button1Pressed, aClick, RightHandSubPath },
260 { QQuick3DXrInputAction::Button2Pressed, yClick, LeftHandSubPath },
261 { QQuick3DXrInputAction::Button2Pressed, bClick, RightHandSubPath },
262 { QQuick3DXrInputAction::Button1Touched, xTouch, LeftHandSubPath },
263 { QQuick3DXrInputAction::Button1Touched, aTouch, RightHandSubPath },
264 { QQuick3DXrInputAction::Button2Touched, yTouch, LeftHandSubPath },
265 { QQuick3DXrInputAction::Button2Touched, bTouch, RightHandSubPath },
266 { QQuick3DXrInputAction::ButtonMenuPressed, menuClick, LeftHandSubPath },
267 { QQuick3DXrInputAction::ButtonSystemPressed, systemClick, RightHandSubPath },
268 { QQuick3DXrInputAction::SqueezeValue, squeezeValue, BothHandsSubPath },
269 { QQuick3DXrInputAction::TriggerValue, triggerValue, BothHandsSubPath },
270 { QQuick3DXrInputAction::TriggerTouched, triggerTouch, BothHandsSubPath },
271 { QQuick3DXrInputAction::ThumbstickX, thumbstickX, BothHandsSubPath },
272 { QQuick3DXrInputAction::ThumbstickY, thumbstickY, BothHandsSubPath },
273 { QQuick3DXrInputAction::ThumbstickPressed, thumbstickClick, BothHandsSubPath },
274 { QQuick3DXrInputAction::ThumbstickTouched, thumbstickTouch, BothHandsSubPath },
275 { QQuick3DXrInputAction::ThumbrestTouched, thumbrestTouch, BothHandsSubPath },
276 };
277
278 XrPath oculusTouchProfile;
279 setPath(path&: oculusTouchProfile, pathString: "/interaction_profiles/oculus/touch_controller");
280 std::vector<XrActionSuggestedBinding> bindings {{
281 {.action: m_handActions.gripPoseAction, .binding: handLeftGripPose},
282 {.action: m_handActions.aimPoseAction, .binding: handLeftAimPose},
283 {.action: m_handActions.hapticAction, .binding: handLeftHaptic},
284
285 {.action: m_handActions.gripPoseAction, .binding: handRightGripPose},
286 {.action: m_handActions.aimPoseAction, .binding: handRightAimPose},
287 {.action: m_handActions.hapticAction, .binding: handRightHaptic},
288 }};
289
290 addToBindings(bindings, mappingDefs);
291
292 XrInteractionProfileSuggestedBinding suggestedBindings{};
293 suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
294 suggestedBindings.interactionProfile = oculusTouchProfile;
295 suggestedBindings.suggestedBindings = bindings.data();
296 suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size();
297 if (!checkXrResult(result: xrSuggestInteractionProfileBindings(instance: m_instance, suggestedBindings: &suggestedBindings)))
298 qWarning(msg: "Failed to get suggested interaction profile bindings for Oculus touch");
299 }
300
301 // Microsoft hand interaction extension as supported by Quest 3
302 // TODO: there are other, very similar, extensions: XR_HTC_HAND_INTERACTION_EXTENSION_NAME and XR_EXT_HAND_INTERACTION_EXTENSION_NAME
303 {
304 XrPath handInteractionProfile;
305 setPath(path&: handInteractionProfile, pathString: "/interaction_profiles/microsoft/hand_interaction");
306 std::vector<XrActionSuggestedBinding> bindings {{
307 {.action: m_handActions.gripPoseAction, .binding: handLeftGripPose},
308 {.action: m_handActions.aimPoseAction, .binding: handLeftAimPose}, // ### Binding succeeds, but does not seem to work on the Quest 3
309 {.action: m_handActions.gripPoseAction, .binding: handRightGripPose},
310 {.action: m_handActions.aimPoseAction, .binding: handRightAimPose},
311 }};
312
313 HandInputMapping mappingDefs {
314 { QQuick3DXrInputAction::SqueezeValue, squeezeValue, BothHandsSubPath },
315 };
316
317 addToBindings(bindings, mappingDefs);
318
319 XrInteractionProfileSuggestedBinding suggestedBindings{};
320 suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
321 suggestedBindings.interactionProfile = handInteractionProfile;
322 suggestedBindings.suggestedBindings = bindings.data();
323 suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size();
324
325 if (!checkXrResult(result: xrSuggestInteractionProfileBindings(instance: m_instance, suggestedBindings: &suggestedBindings)))
326 qWarning(msg: "Failed to get suggested interaction profile bindings for MSFT hand interaction");
327 }
328
329 {
330 XrPath htcViveProfile;
331 setPath(path&: htcViveProfile, pathString: "/interaction_profiles/htc/vive_controller");
332
333 HandInputMapping mappingDefs {
334 { QQuick3DXrInputAction::ButtonMenuPressed, menuClick, BothHandsSubPath },
335 { QQuick3DXrInputAction::ButtonSystemPressed, systemClick, BothHandsSubPath },
336 { QQuick3DXrInputAction::SqueezePressed, squeezeClick, BothHandsSubPath },
337 { QQuick3DXrInputAction::TriggerValue, triggerValue, BothHandsSubPath },
338 { QQuick3DXrInputAction::TriggerPressed, triggerClick, BothHandsSubPath },
339 { QQuick3DXrInputAction::TrackpadX, trackpadX, BothHandsSubPath },
340 { QQuick3DXrInputAction::TrackpadY, trackpadY, BothHandsSubPath },
341 { QQuick3DXrInputAction::TrackpadPressed, trackpadClick, BothHandsSubPath },
342 { QQuick3DXrInputAction::TrackpadTouched, trackpadTouch, BothHandsSubPath },
343 };
344
345 std::vector<XrActionSuggestedBinding> bindings {{
346 {.action: m_handActions.gripPoseAction, .binding: handLeftGripPose},
347 {.action: m_handActions.aimPoseAction, .binding: handLeftAimPose},
348 {.action: m_handActions.hapticAction, .binding: handLeftHaptic},
349
350 {.action: m_handActions.gripPoseAction, .binding: handRightGripPose},
351 {.action: m_handActions.aimPoseAction, .binding: handRightAimPose},
352 {.action: m_handActions.hapticAction, .binding: handRightHaptic},
353 }};
354
355 addToBindings(bindings, mappingDefs);
356
357 XrInteractionProfileSuggestedBinding suggestedBindings{};
358 suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
359 suggestedBindings.interactionProfile = htcViveProfile;
360 suggestedBindings.suggestedBindings = bindings.data();
361 suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size();
362 if (!checkXrResult(result: xrSuggestInteractionProfileBindings(instance: m_instance, suggestedBindings: &suggestedBindings)))
363 qWarning(msg: "Failed to get suggested interaction profile bindings for Vive controller");
364 }
365
366 // Microsoft MRM ### TODO
367 {
368 XrPath microsoftMotionProfile;
369 setPath(path&: microsoftMotionProfile, pathString: "/interaction_profiles/microsoft/motion_controller");
370 }
371
372 // Valve Index ### TODO
373 {
374 XrPath valveIndexProfile;
375 setPath(path&: valveIndexProfile, pathString: "/interaction_profiles/valve/index_controller");
376 }
377
378 // Setup Action Spaces
379
380 XrActionSpaceCreateInfo actionSpaceInfo{};
381 actionSpaceInfo.type = XR_TYPE_ACTION_SPACE_CREATE_INFO;
382 actionSpaceInfo.action = m_handActions.gripPoseAction;
383 actionSpaceInfo.poseInActionSpace.orientation.w = 1.0f;
384 //actionSpaceInfo.poseInActionSpace.orientation.y = 1.0f;
385 actionSpaceInfo.subactionPath = m_handSubactionPath[0];
386 if (!checkXrResult(result: xrCreateActionSpace(session: m_session, createInfo: &actionSpaceInfo, space: &m_handGripSpace[0])))
387 qWarning(msg: "Failed to create action space for handGripSpace[0]");
388 actionSpaceInfo.subactionPath = m_handSubactionPath[1];
389 if (!checkXrResult(result: xrCreateActionSpace(session: m_session, createInfo: &actionSpaceInfo, space: &m_handGripSpace[1])))
390 qWarning(msg: "Failed to create action space for handGripSpace[1]");
391
392 actionSpaceInfo.action = m_handActions.aimPoseAction;
393 actionSpaceInfo.subactionPath = m_handSubactionPath[0];
394 if (!checkXrResult(result: xrCreateActionSpace(session: m_session, createInfo: &actionSpaceInfo, space: &m_handAimSpace[0])))
395 qWarning(msg: "Failed to create action space for handAimSpace[0]");
396 actionSpaceInfo.subactionPath = m_handSubactionPath[1];
397 if (!checkXrResult(result: xrCreateActionSpace(session: m_session, createInfo: &actionSpaceInfo, space: &m_handAimSpace[1])))
398 qWarning(msg: "Failed to create action space for handAimSpace[1]");
399
400 // Attach Action set to session
401
402 XrSessionActionSetsAttachInfo attachInfo{};
403 attachInfo.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO;
404 attachInfo.countActionSets = 1;
405 attachInfo.actionSets = &m_actionSet;
406 if (!checkXrResult(result: xrAttachSessionActionSets(session: m_session, attachInfo: &attachInfo)))
407 qWarning(msg: "Failed to attach action sets to session");
408
409 m_initialized = true;
410}
411
412void QQuick3DXrInputManagerPrivate::teardown()
413{
414 if (!m_initialized)
415 return;
416
417 m_initialized = false;
418
419 xrDestroySpace(space: m_handGripSpace[0]);
420 xrDestroySpace(space: m_handGripSpace[1]);
421 xrDestroySpace(space: m_handAimSpace[0]);
422 xrDestroySpace(space: m_handAimSpace[1]);
423
424 destroyActions();
425
426 if (xrDestroyHandTrackerEXT_) {
427 xrDestroyHandTrackerEXT_(handTracker[Hand::LeftHand]);
428 xrDestroyHandTrackerEXT_(handTracker[Hand::RightHand]);
429 }
430
431 m_instance = {XR_NULL_HANDLE};
432 m_session = {XR_NULL_HANDLE};
433}
434
435QQuick3DXrInputManagerPrivate *QQuick3DXrInputManagerPrivate::get(QQuick3DXrInputManager *inputManager)
436{
437 QSSG_ASSERT(inputManager != nullptr, return nullptr);
438 return inputManager->d_func();
439}
440
441void QQuick3DXrInputManagerPrivate::pollActions()
442{
443 if (!m_initialized)
444 return;
445
446 // Sync Actions
447 const XrActiveActionSet activeActionSet{.actionSet: m_actionSet, XR_NULL_PATH};
448 XrActionsSyncInfo syncInfo{};
449 syncInfo.type = XR_TYPE_ACTIONS_SYNC_INFO;
450 syncInfo.countActiveActionSets = 1;
451 syncInfo.activeActionSets = &activeActionSet;
452 XrResult result = xrSyncActions(session: m_session, syncInfo: &syncInfo);
453 if (!(result == XR_SUCCESS ||
454 result == XR_SESSION_LOSS_PENDING ||
455 result == XR_SESSION_NOT_FOCUSED))
456 {
457 if (!checkXrResult(result)) {
458 qWarning(msg: "xrSyncActions failed");
459 return;
460 }
461 }
462
463 // Hands
464 XrActionStateGetInfo getInfo{};
465 getInfo.type = XR_TYPE_ACTION_STATE_GET_INFO;
466 for (auto hand : {Hand::LeftHand, Hand::RightHand}) {
467
468 getInfo.subactionPath = m_handSubactionPath[hand];
469 auto &inputState = m_handInputState[hand];
470
471 for (const auto &def : m_handInputActionDefs) {
472 getInfo.action = m_inputActions[def.id];
473 switch (def.type) {
474 case XR_ACTION_TYPE_BOOLEAN_INPUT: {
475 XrActionStateBoolean boolValue{};
476 boolValue.type = XR_TYPE_ACTION_STATE_BOOLEAN;
477 if (checkXrResult(result: xrGetActionStateBoolean(session: m_session, getInfo: &getInfo, state: &boolValue))) {
478 if (boolValue.isActive && boolValue.changedSinceLastSync) {
479 //qDebug() << "ACTION" << i << def.shortName << bool(boolValue.currentState);
480 setInputValue(hand, id: def.id, shortName: def.shortName, value: float(boolValue.currentState));
481 }
482 } else {
483 qWarning(msg: "Failed to get action state for bool hand input");
484 }
485 break;
486 }
487 case XR_ACTION_TYPE_FLOAT_INPUT: {
488 XrActionStateFloat floatValue{};
489 floatValue.type = XR_TYPE_ACTION_STATE_FLOAT;
490 if (checkXrResult(result: xrGetActionStateFloat(session: m_session, getInfo: &getInfo, state: &floatValue))) {
491 if (floatValue.isActive && floatValue.changedSinceLastSync) {
492 //qDebug() << "ACTION" << i << def.shortName << floatValue.currentState;
493 setInputValue(hand, id: def.id, shortName: def.shortName, value: float(floatValue.currentState));
494 }
495 } else {
496 qWarning(msg: "Failed to get action state for float hand input");
497 }
498 break;
499 }
500 case XR_ACTION_TYPE_VECTOR2F_INPUT:
501 case XR_ACTION_TYPE_POSE_INPUT:
502 case XR_ACTION_TYPE_VIBRATION_OUTPUT:
503 case XR_ACTION_TYPE_MAX_ENUM:
504 break;
505 }
506 }
507
508 // Get pose activity status
509 getInfo.action = m_handActions.gripPoseAction;
510 XrActionStatePose poseState{};
511 poseState.type = XR_TYPE_ACTION_STATE_POSE;
512 if (checkXrResult(result: xrGetActionStatePose(session: m_session, getInfo: &getInfo, state: &poseState)))
513 inputState->setIsActive(poseState.isActive);
514 else
515 qWarning(msg: "Failed to get action state pose");
516
517 // TODO handle any output as well here (haptics)
518 // XrAction gripPoseAction{XR_NULL_HANDLE};
519 // XrAction aimPoseAction{XR_NULL_HANDLE};
520 // XrAction hapticAction{XR_NULL_HANDLE};
521
522 }
523
524}
525
526void QQuick3DXrInputManagerPrivate::updatePoses(XrTime predictedDisplayTime, XrSpace appSpace)
527{
528 // Update the Hands pose
529
530 for (auto poseSpace : {HandPoseSpace::AimPose, HandPoseSpace::GripPose}) {
531 for (auto hand : {Hand::LeftHand, Hand::RightHand}) {
532 if (!isPoseInUse(hand, poseSpace))
533 continue;
534 XrSpaceLocation spaceLocation{};
535 spaceLocation.type = XR_TYPE_SPACE_LOCATION;
536 XrResult res;
537 res = xrLocateSpace(space: handSpace(hand, poseSpace), baseSpace: appSpace, time: predictedDisplayTime, location: &spaceLocation);
538 // qDebug() << "LOCATE SPACE hand:" << hand << "res" << res << "flags" << spaceLocation.locationFlags
539 // << "active" << m_handInputState[hand]->isActive()
540 // << "Pos" << spaceLocation.pose.position.x << spaceLocation.pose.position.y << spaceLocation.pose.position.z;
541 m_validAimStateFromUpdatePoses[hand] = poseSpace == HandPoseSpace::AimPose
542 && XR_UNQUALIFIED_SUCCESS(res) && (spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT)
543 && (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT); // ### Workaround for Quest issue with hand interaction aim pose
544
545 if (XR_UNQUALIFIED_SUCCESS(res)) {
546 if ((spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 &&
547 (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0) {
548
549 // Update hand transform
550 setPosePositionAndRotation(hand, poseSpace,
551 position: QVector3D(spaceLocation.pose.position.x,
552 spaceLocation.pose.position.y,
553 spaceLocation.pose.position.z) * 100.0f,
554 rotation: QQuaternion(spaceLocation.pose.orientation.w,
555 spaceLocation.pose.orientation.x,
556 spaceLocation.pose.orientation.y,
557 spaceLocation.pose.orientation.z));
558 }
559 } else {
560 // Tracking loss is expected when the hand is not active so only log a message
561 // if the hand is active.
562 if (isHandActive(hand)) {
563 const char* handName[] = {"left", "right"};
564 qCDebug(lcQuick3DXr, "Unable to locate %s hand action space in app space: %d", handName[hand], res);
565 }
566 }
567 }
568 }
569}
570
571void QQuick3DXrInputManagerPrivate::updateHandtracking(XrTime predictedDisplayTime, XrSpace appSpace, bool aimExtensionEnabled)
572{
573 if (xrLocateHandJointsEXT_) {
574
575 XrHandTrackingAimStateFB aimState[2] = {{}, {}}; // Only used when aim extension is enabled
576 XrHandJointVelocitiesEXT velocities[2]{{}, {}};
577 XrHandJointLocationsEXT locations[2]{{}, {}};
578 XrHandJointsLocateInfoEXT locateInfo[2] = {{}, {}};
579
580 for (auto hand : {Hand::LeftHand, Hand::RightHand}) {
581 if (handTracker[hand] == XR_NULL_HANDLE)
582 continue;
583
584 aimState[hand].type = XR_TYPE_HAND_TRACKING_AIM_STATE_FB;
585
586 velocities[hand].type = XR_TYPE_HAND_JOINT_VELOCITIES_EXT;
587 velocities[hand].jointCount = XR_HAND_JOINT_COUNT_EXT;
588 velocities[hand].jointVelocities = jointVelocities[hand];
589 velocities[hand].next = aimExtensionEnabled ? &aimState[hand] : nullptr;
590
591 locations[hand].type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT;
592 locations[hand].next = &velocities[hand];
593 locations[hand].jointCount = XR_HAND_JOINT_COUNT_EXT;
594 locations[hand].jointLocations = jointLocations[hand];
595
596 locateInfo[hand].type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT;
597 locateInfo[hand].baseSpace = appSpace;
598 locateInfo[hand].time = predictedDisplayTime;
599 if (!checkXrResult(result: xrLocateHandJointsEXT_(handTracker[hand], &locateInfo[hand], &locations[hand])))
600 qWarning(msg: "Failed to locate hand joints for hand tracker");
601
602 QList<QVector3D> jp;
603 jp.reserve(XR_HAND_JOINT_COUNT_EXT);
604 QList<QQuaternion> jr;
605 jr.reserve(XR_HAND_JOINT_COUNT_EXT);
606 for (uint i = 0; i < locations[hand].jointCount; ++i) {
607 auto &pose = jointLocations[hand][i].pose;
608 jp.append(t: OpenXRHelpers::toQVector(v: pose.position));
609 jr.append(t: OpenXRHelpers::toQQuaternion(q: pose.orientation));
610 }
611 m_handInputState[hand]->setJointPositionsAndRotations(newJointPositions: jp, newJointRotations: jr);
612 m_handInputState[hand]->setIsHandTrackingActive(locations[hand].isActive);
613 }
614
615 if (aimExtensionEnabled) {
616 // Finger pinch handling
617 for (auto hand : {Hand::LeftHand, Hand::RightHand}) {
618 const uint state = aimState[hand].status;
619 const uint oldState = m_aimStateFlags[hand];
620 auto updateState = [&](const char *name, QQuick3DXrInputAction::Action id, uint flag) {
621 if ((state & flag) != (oldState & flag))
622 setInputValue(hand, id, shortName: name, value: float(!!(state & flag)));
623 };
624
625 updateState("index_pinch", QQuick3DXrInputAction::IndexFingerPinch, XR_HAND_TRACKING_AIM_INDEX_PINCHING_BIT_FB);
626 updateState("middle_pinch", QQuick3DXrInputAction::MiddleFingerPinch, XR_HAND_TRACKING_AIM_MIDDLE_PINCHING_BIT_FB);
627 updateState("ring_pinch", QQuick3DXrInputAction::RingFingerPinch, XR_HAND_TRACKING_AIM_RING_PINCHING_BIT_FB);
628 updateState("little_pinch", QQuick3DXrInputAction::LittleFingerPinch, XR_HAND_TRACKING_AIM_LITTLE_PINCHING_BIT_FB);
629 updateState("hand_tracking_menu_press", QQuick3DXrInputAction::HandTrackingMenuPress, XR_HAND_TRACKING_AIM_MENU_PRESSED_BIT_FB);
630 m_aimStateFlags[hand] = state;
631 }
632
633 // ### Workaround for Quest issue with hand interaction aim pose
634 for (auto hand : {Hand::LeftHand, Hand::RightHand}) {
635 if (isPoseInUse(hand, poseSpace: HandPoseSpace::AimPose) && !m_validAimStateFromUpdatePoses[hand]) {
636 if ((aimState[hand].status & XR_HAND_TRACKING_AIM_VALID_BIT_FB)) {
637 setPosePositionAndRotation(hand, poseSpace: HandPoseSpace::AimPose,
638 position: QVector3D(aimState[hand].aimPose.position.x,
639 aimState[hand].aimPose.position.y,
640 aimState[hand].aimPose.position.z) * 100.0f,
641 rotation: QQuaternion(aimState[hand].aimPose.orientation.w,
642 aimState[hand].aimPose.orientation.x,
643 aimState[hand].aimPose.orientation.y,
644 aimState[hand].aimPose.orientation.z));
645 m_handInputState[hand]->setIsActive(true); // TODO: clean up
646 }
647 }
648 }
649 }
650 }
651}
652
653void QQuick3DXrInputManagerPrivate::setupHandTracking()
654{
655 resolveXrFunction(
656 name: "xrCreateHandTrackerEXT",
657 function: (PFN_xrVoidFunction*)(&xrCreateHandTrackerEXT_));
658 resolveXrFunction(
659 name: "xrDestroyHandTrackerEXT",
660 function: (PFN_xrVoidFunction*)(&xrDestroyHandTrackerEXT_));
661 resolveXrFunction(
662 name: "xrLocateHandJointsEXT",
663 function: (PFN_xrVoidFunction*)(&xrLocateHandJointsEXT_));
664 resolveXrFunction(
665 name: "xrGetHandMeshFB",
666 function: (PFN_xrVoidFunction*)(&xrGetHandMeshFB_));
667
668 if (xrCreateHandTrackerEXT_) {
669 XrHandTrackerCreateInfoEXT createInfo{};
670 createInfo.type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT;
671 createInfo.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT;
672 createInfo.hand = XR_HAND_LEFT_EXT;
673 if (!checkXrResult(result: xrCreateHandTrackerEXT_(m_session, &createInfo, &handTracker[QtQuick3DXr::LeftHand])))
674 qWarning(msg: "Failed to create left hand tracker");
675 createInfo.hand = XR_HAND_RIGHT_EXT;
676 if (!checkXrResult(result: xrCreateHandTrackerEXT_(m_session, &createInfo, &handTracker[QtQuick3DXr::RightHand])))
677 qWarning(msg: "Failed to create right hand tracker");
678 }
679 if (xrGetHandMeshFB_) {
680 for (auto hand : {Hand::LeftHand, Hand::RightHand}) {
681 if (queryHandMesh(hand))
682 createHandModelData(hand);
683 }
684 }
685}
686
687bool QQuick3DXrInputManagerPrivate::queryHandMesh(Hand hand)
688{
689 XrHandTrackingMeshFB mesh {};
690 mesh.type = XR_TYPE_HAND_TRACKING_MESH_FB;
691 // Left hand
692 if (!checkXrResult(result: xrGetHandMeshFB_(handTracker[hand], &mesh))) {
693 qWarning(msg: "Failed to query hand mesh info.");
694 return false;
695 }
696
697 mesh.jointCapacityInput = mesh.jointCountOutput;
698 mesh.vertexCapacityInput = mesh.vertexCountOutput;
699 mesh.indexCapacityInput = mesh.indexCountOutput;
700 m_handMeshData[hand].vertexPositions.resize(size: mesh.vertexCapacityInput);
701 m_handMeshData[hand].vertexNormals.resize(size: mesh.vertexCapacityInput);
702 m_handMeshData[hand].vertexUVs.resize(size: mesh.vertexCapacityInput);
703 m_handMeshData[hand].vertexBlendIndices.resize(size: mesh.vertexCapacityInput);
704 m_handMeshData[hand].vertexBlendWeights.resize(size: mesh.vertexCapacityInput);
705 m_handMeshData[hand].indices.resize(size: mesh.indexCapacityInput);
706 mesh.jointBindPoses = m_handMeshData[hand].jointBindPoses;
707 mesh.jointParents = m_handMeshData[hand].jointParents;
708 mesh.jointRadii = m_handMeshData[hand].jointRadii;
709 mesh.vertexPositions = m_handMeshData[hand].vertexPositions.data();
710 mesh.vertexNormals = m_handMeshData[hand].vertexNormals.data();
711 mesh.vertexUVs = m_handMeshData[hand].vertexUVs.data();
712 mesh.vertexBlendIndices = m_handMeshData[hand].vertexBlendIndices.data();
713 mesh.vertexBlendWeights = m_handMeshData[hand].vertexBlendWeights.data();
714 mesh.indices = m_handMeshData[hand].indices.data();
715
716 if (!checkXrResult(result: xrGetHandMeshFB_(handTracker[hand], &mesh))) {
717 qWarning(msg: "Failed to get hand mesh data.");
718 return false;
719 }
720
721 return true;
722};
723
724void QQuick3DXrInputManagerPrivate::setupActions()
725{
726 m_handInputActionDefs = {
727 { .id: QQuick3DXrInputAction::Button1Pressed, .shortName: "b1_pressed", .localizedName: "Button 1 Pressed", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
728 { .id: QQuick3DXrInputAction::Button1Touched, .shortName: "b1_touched", .localizedName: "Button 1 Touched", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
729 { .id: QQuick3DXrInputAction::Button2Pressed, .shortName: "b2_pressed", .localizedName: "Button 2 Pressed", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
730 { .id: QQuick3DXrInputAction::Button2Touched, .shortName: "b2_touched", .localizedName: "Button 2 Touched", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
731 { .id: QQuick3DXrInputAction::ButtonMenuPressed, .shortName: "bmenu_pressed", .localizedName: "Button Menu Pressed", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
732 { .id: QQuick3DXrInputAction::ButtonMenuTouched, .shortName: "bmenu_touched", .localizedName: "Button Menu Touched", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
733 { .id: QQuick3DXrInputAction::ButtonSystemPressed, .shortName: "bsystem_pressed", .localizedName: "Button System Pressed", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
734 { .id: QQuick3DXrInputAction::ButtonSystemTouched, .shortName: "bsystem_touched", .localizedName: "Button System Touched", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
735 { .id: QQuick3DXrInputAction::SqueezeValue, .shortName: "squeeze_value", .localizedName: "Squeeze Value", .type: XR_ACTION_TYPE_FLOAT_INPUT },
736 { .id: QQuick3DXrInputAction::SqueezeForce, .shortName: "squeeze_force", .localizedName: "Squeeze Force", .type: XR_ACTION_TYPE_FLOAT_INPUT },
737 { .id: QQuick3DXrInputAction::SqueezePressed, .shortName: "squeeze_pressed", .localizedName: "Squeeze Pressed", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
738 { .id: QQuick3DXrInputAction::TriggerValue, .shortName: "trigger_value", .localizedName: "Trigger Value", .type: XR_ACTION_TYPE_FLOAT_INPUT },
739 { .id: QQuick3DXrInputAction::TriggerPressed, .shortName: "trigger_pressed", .localizedName: "Trigger Pressed", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
740 { .id: QQuick3DXrInputAction::TriggerTouched, .shortName: "trigger_touched", .localizedName: "Trigger Touched", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
741 { .id: QQuick3DXrInputAction::ThumbstickX, .shortName: "thumbstick_x", .localizedName: "Thumbstick X", .type: XR_ACTION_TYPE_FLOAT_INPUT },
742 { .id: QQuick3DXrInputAction::ThumbstickY, .shortName: "thumbstick_y", .localizedName: "Thumbstick Y", .type: XR_ACTION_TYPE_FLOAT_INPUT },
743 { .id: QQuick3DXrInputAction::ThumbstickPressed, .shortName: "thumbstick_pressed", .localizedName: "Thumbstick Pressed", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
744 { .id: QQuick3DXrInputAction::ThumbstickTouched, .shortName: "thumbstick_touched", .localizedName: "Thumbstick Touched", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
745 { .id: QQuick3DXrInputAction::ThumbrestTouched, .shortName: "thumbrest_touched", .localizedName: "Thumbrest Touched", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
746 { .id: QQuick3DXrInputAction::TrackpadX, .shortName: "trackpad_x", .localizedName: "Trackpad X", .type: XR_ACTION_TYPE_FLOAT_INPUT },
747 { .id: QQuick3DXrInputAction::TrackpadY, .shortName: "trackpad_y", .localizedName: "Trackpad Y", .type: XR_ACTION_TYPE_FLOAT_INPUT },
748 { .id: QQuick3DXrInputAction::TrackpadForce, .shortName: "trackpad_force", .localizedName: "Trackpad Force", .type: XR_ACTION_TYPE_FLOAT_INPUT },
749 { .id: QQuick3DXrInputAction::TrackpadTouched, .shortName: "trackpad_touched", .localizedName: "Trackpad Touched", .type: XR_ACTION_TYPE_BOOLEAN_INPUT },
750 { .id: QQuick3DXrInputAction::TrackpadPressed, .shortName: "trackpad_pressed", .localizedName: "Trackpad Pressed", .type: XR_ACTION_TYPE_BOOLEAN_INPUT }
751 };
752
753 // Create an action set.
754 {
755 XrActionSetCreateInfo actionSetInfo{};
756 actionSetInfo.type = XR_TYPE_ACTION_SET_CREATE_INFO;
757 strcpy(dest: actionSetInfo.actionSetName, src: "gameplay");
758 strcpy(dest: actionSetInfo.localizedActionSetName, src: "Gameplay");
759 actionSetInfo.priority = 0;
760 if (!checkXrResult(result: xrCreateActionSet(instance: m_instance, createInfo: &actionSetInfo, actionSet: &m_actionSet)))
761 qWarning(msg: "Failed to create gameplay action set");
762 }
763
764 // Create Hand Actions
765 setPath(path&: m_handSubactionPath[0], pathString: "/user/hand/left");
766 setPath(path&: m_handSubactionPath[1], pathString: "/user/hand/right");
767
768 for (const auto &def : m_handInputActionDefs) {
769 createAction(type: def.type,
770 name: def.shortName,
771 localizedName: def.localizedName,
772 numSubactions: 2,
773 subactionPath: m_handSubactionPath,
774 action&: m_inputActions[def.id]);
775 }
776
777 createAction(type: XR_ACTION_TYPE_VIBRATION_OUTPUT,
778 name: "vibrate_hand",
779 localizedName: "Vibrate Hand",
780 numSubactions: 2,
781 subactionPath: m_handSubactionPath,
782 action&: m_handActions.hapticAction);
783 createAction(type: XR_ACTION_TYPE_POSE_INPUT,
784 name: "hand_grip_pose",
785 localizedName: "Hand Grip Pose",
786 numSubactions: 2,
787 subactionPath: m_handSubactionPath,
788 action&: m_handActions.gripPoseAction);
789 createAction(type: XR_ACTION_TYPE_POSE_INPUT,
790 name: "hand_aim_pose",
791 localizedName: "Hand Aim Pose",
792 numSubactions: 2,
793 subactionPath: m_handSubactionPath,
794 action&: m_handActions.aimPoseAction);
795
796}
797
798void QQuick3DXrInputManagerPrivate::destroyActions()
799{
800 for (auto &action : m_inputActions) {
801 if (action)
802 xrDestroyAction(action);
803 }
804
805 xrDestroyAction(action: m_handActions.gripPoseAction);
806 xrDestroyAction(action: m_handActions.aimPoseAction);
807 xrDestroyAction(action: m_handActions.hapticAction);
808
809 xrDestroyActionSet(actionSet: m_actionSet);
810}
811
812bool QQuick3DXrInputManagerPrivate::checkXrResult(const XrResult &result)
813{
814 return OpenXRHelpers::checkXrResult(result, instance: m_instance);
815}
816
817bool QQuick3DXrInputManagerPrivate::resolveXrFunction(const char *name, PFN_xrVoidFunction *function)
818{
819 XrResult result = xrGetInstanceProcAddr(instance: m_instance, name, function);
820 if (!OpenXRHelpers::checkXrResult(result, instance: m_instance)) {
821 qWarning(msg: "Failed to resolve OpenXR function %s", name);
822 *function = nullptr;
823 return false;
824 }
825 return true;
826}
827
828void QQuick3DXrInputManagerPrivate::setPath(XrPath &path, const QByteArray &pathString)
829{
830 if (!checkXrResult(result: xrStringToPath(instance: m_instance, pathString: pathString.constData(), path: &path)))
831 qWarning(msg: "xrStringToPath failed");
832}
833
834void QQuick3DXrInputManagerPrivate::createAction(XrActionType type,
835 const char *name,
836 const char *localizedName,
837 int numSubactions,
838 XrPath *subactionPath,
839 XrAction &action)
840{
841 XrActionCreateInfo actionInfo{};
842 actionInfo.type = XR_TYPE_ACTION_CREATE_INFO;
843 actionInfo.actionType = type;
844 strcpy(dest: actionInfo.actionName, src: name);
845 strcpy(dest: actionInfo.localizedActionName, src: localizedName);
846 actionInfo.countSubactionPaths = quint32(numSubactions);
847 actionInfo.subactionPaths = subactionPath;
848 if (!checkXrResult(result: xrCreateAction(actionSet: m_actionSet, createInfo: &actionInfo, action: &action)))
849 qCDebug(lcQuick3DXr) << "xrCreateAction failed. Name:" << name << "localizedName:" << localizedName;
850}
851
852void QQuick3DXrInputManagerPrivate::getBoolInputState(XrActionStateGetInfo &getInfo, const XrAction &action, std::function<void(bool)> setter)
853{
854 getInfo.action = action;
855 XrActionStateBoolean boolValue{};
856 boolValue.type = XR_TYPE_ACTION_STATE_BOOLEAN;
857 if (checkXrResult(result: xrGetActionStateBoolean(session: m_session, getInfo: &getInfo, state: &boolValue))) {
858 if (boolValue.isActive == XR_TRUE)
859 setter(bool(boolValue.currentState));
860 } else {
861 qWarning(msg: "Failed to get action state: bool");
862 }
863}
864
865void QQuick3DXrInputManagerPrivate::getFloatInputState(XrActionStateGetInfo &getInfo, const XrAction &action, std::function<void(float)> setter)
866{
867 getInfo.action = action;
868 XrActionStateFloat floatValue{};
869 floatValue.type = XR_TYPE_ACTION_STATE_FLOAT;
870 if (checkXrResult(result: xrGetActionStateFloat(session: m_session, getInfo: &getInfo, state: &floatValue))) {
871 if (floatValue.isActive == XR_TRUE)
872 setter(float(floatValue.currentState));
873 } else {
874 qWarning(msg: "Failed to get action state: float");
875 }
876}
877
878XrSpace QQuick3DXrInputManagerPrivate::handSpace(QQuick3DXrInputManagerPrivate::Hand hand, HandPoseSpace poseSpace)
879{
880 if (poseSpace == HandPoseSpace::GripPose)
881 return m_handGripSpace[hand];
882 else
883 return m_handAimSpace[hand];
884}
885
886bool QQuick3DXrInputManagerPrivate::isHandActive(QQuick3DXrInputManagerPrivate::Hand hand)
887{
888 return m_handInputState[hand]->isActive();
889}
890
891bool QQuick3DXrInputManagerPrivate::isHandTrackerActive(Hand hand)
892{
893 return m_handInputState[hand]->isHandTrackingActive();
894}
895
896void QQuick3DXrInputManagerPrivate::setPosePositionAndRotation(Hand hand, HandPoseSpace poseSpace, const QVector3D &position, const QQuaternion &rotation)
897{
898 for (auto *controller : std::as_const(t&: m_controllers)) {
899 if (QtQuick3DXr::handForController(controller: controller->controller()) == hand && QtQuick3DXr::pose_cast(poseSpace: controller->poseSpace()) == poseSpace) {
900 controller->setPosition(position);
901 controller->setRotation(rotation);
902 }
903 }
904}
905
906void QQuick3DXrInputManagerPrivate::setInputValue(Hand hand, int id, const char *shortName, float value)
907{
908 QSSG_ASSERT(hand < 2, hand = Hand::LeftHand);
909 QQuick3DXrActionMapper::handleInput(id: QQuick3DXrInputAction::Action(id), hand: static_cast<QQuick3DXrInputAction::Hand>(hand), shortName, value);
910}
911
912QQuick3DXrHandInput *QQuick3DXrInputManagerPrivate::leftHandInput() const
913{
914 return m_handInputState[Hand::LeftHand];
915}
916
917QQuick3DXrHandInput *QQuick3DXrInputManagerPrivate::rightHandInput() const
918{
919 return m_handInputState[Hand::RightHand];
920}
921
922static inline QMatrix4x4 transformMatrix(const QVector3D &position, const QQuaternion &rotation)
923{
924 QMatrix4x4 transform = QMatrix4x4{rotation.toRotationMatrix()};
925
926 transform(0, 3) += position[0];
927 transform(1, 3) += position[1];
928 transform(2, 3) += position[2];
929
930 return transform;
931}
932
933void QQuick3DXrInputManagerPrivate::setupHandModelInternal(QQuick3DXrHandModel *model, Hand hand)
934{
935 QQuick3DGeometry *geometry = m_handGeometryData[hand].geometry;
936 if (!geometry)
937 return;
938
939 model->setGeometry(geometry);
940
941 QQuick3DSkin *skin = new QQuick3DSkin(model);
942 auto jointListProp = skin->joints();
943 QList<QMatrix4x4> inverseBindPoses;
944 inverseBindPoses.reserve(XR_HAND_JOINT_COUNT_EXT);
945
946 const auto &handMeshData = m_handMeshData[hand];
947
948 for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; ++i) {
949 const auto &pose = handMeshData.jointBindPoses[i];
950 const QVector3D pos = OpenXRHelpers::toQVector(v: pose.position);
951 const QQuaternion rot = OpenXRHelpers::toQQuaternion(q: pose.orientation);
952 inverseBindPoses.append(t: transformMatrix(position: pos, rotation: rot).inverted());
953 QQuick3DNode *joint = new QQuick3DNode(model);
954 joint->setPosition(pos);
955 joint->setRotation(rot);
956 jointListProp.append(&jointListProp, joint);
957 }
958 skin->setInverseBindPoses(inverseBindPoses);
959 model->setSkin(skin);
960}
961
962void QQuick3DXrInputManagerPrivate::setupHandModel(QQuick3DXrHandModel *model)
963{
964 QSSG_ASSERT(model != nullptr, return);
965
966 if (model->geometry() != nullptr || model->skin() != nullptr) {
967 qWarning() << "Hand model already has geometry or skin set.";
968 return;
969 }
970
971 auto hand = model->hand();
972 if (hand == QQuick3DXrHandModel::LeftHand)
973 setupHandModelInternal(model, hand: Hand::LeftHand);
974 else if (hand == QQuick3DXrHandModel::RightHand)
975 setupHandModelInternal(model, hand: Hand::RightHand);
976 else
977 qWarning() << "No matching hand tracker input found for hand model.";
978}
979
980// Used both to add a new controller, and notify that an existing one has changed
981void QQuick3DXrInputManagerPrivate::registerController(QQuick3DXrController *controller)
982{
983 m_poseUsageDirty = true;
984 if (controller->controller() == QQuick3DXrController::ControllerNone) {
985 m_controllers.remove(value: controller);
986 return;
987 }
988 // No point in checking whether it's already in the set: that's just as expensive as inserting
989 m_controllers.insert(value: controller);
990}
991
992void QQuick3DXrInputManagerPrivate::unregisterController(QQuick3DXrController *controller)
993{
994 m_poseUsageDirty = m_controllers.remove(value: controller);
995}
996
997bool QQuick3DXrInputManagerPrivate::isPoseInUse(Hand hand, HandPoseSpace poseSpace)
998{
999 QSSG_ASSERT(uint(hand) < 2 && uint(poseSpace) < 2, return false);
1000 if (m_poseUsageDirty) {
1001 std::fill_n(first: &m_poseInUse[0][0], n: 4, value: false);
1002 for (const auto *controller : std::as_const(t&: m_controllers)) {
1003 m_poseInUse[uint(controller->controller())][uint(controller->poseSpace())] = true;
1004 }
1005 m_poseUsageDirty = false;
1006 }
1007 return m_poseInUse[uint(hand)][uint(poseSpace)];
1008}
1009
1010void QQuick3DXrInputManagerPrivate::createHandModelData(Hand hand)
1011{
1012 const auto &handMeshData = m_handMeshData[hand];
1013
1014 auto &geometry = m_handGeometryData[hand].geometry;
1015 delete geometry;
1016 geometry = createHandMeshGeometry(handMeshData);
1017}
1018
1019QT_END_NAMESPACE
1020

source code of qtquick3d/src/xr/quick3dxr/openxr/qopenxrinputmanager.cpp