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

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