Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qopenxrinputmanager.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "../qquick3dxrinputmanager_p.h"
6#include "openxr/qopenxrhelpers_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
19
20Q_DECLARE_LOGGING_CATEGORY(lcQuick3DXr);
21
23 : q_ptr(&manager)
24{
25 m_handInputState[Hand::LeftHand] = new QQuick3DXrHandInput(this);
26 m_handInputState[Hand::RightHand] = new QQuick3DXrHandInput(this);
27}
28
30{
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(res.paths[Hand::LeftHand], "/user/hand/left/" + path);
43 setPath(res.paths[Hand::RightHand], "/user/hand/right/" + path);
44 return res;
45}
46
47
48XrPath QQuick3DXrInputManagerPrivate::makeInputPath(const QByteArrayView path)
49{
50 XrPath res;
51 setPath(res, 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(QQuick3DGeometry::Attribute::PositionSemantic, offset, QQuick3DGeometry::Attribute::ComponentType::F32Type);
72 offset += 3 * sizeof(float);
73 }
74
75 if (hasNormals) {
76 geometry->addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, offset, QQuick3DGeometry::Attribute::ComponentType::F32Type);
77 offset += 3 * sizeof(float);
78 }
79
80 if (hasUV0s) {
81 geometry->addAttribute(QQuick3DGeometry::Attribute::TexCoordSemantic, offset, QQuick3DGeometry::Attribute::ComponentType::F32Type);
82 offset += 2 * sizeof(float);
83 }
84
85 if (hasJoints) {
86 geometry->addAttribute(QQuick3DGeometry::Attribute::JointSemantic, offset, QQuick3DGeometry::Attribute::ComponentType::I32Type);
87 offset += 4 * sizeof(qint32);
88 }
89
90 if (hasWeights) {
91 geometry->addAttribute(QQuick3DGeometry::Attribute::WeightSemantic, offset, QQuick3DGeometry::Attribute::ComponentType::F32Type);
92 offset += 4 * sizeof(float);
93 }
94
95 if (hasIndexes)
96 geometry->addAttribute(QQuick3DGeometry::Attribute::IndexSemantic, 0, 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(bufferSize);
105
106 QVector3D minBounds;
107 QVector3D maxBounds;
108
109 auto appendFloat = [&vertexBuffer](float f) {
110 vertexBuffer.append(reinterpret_cast<const char *>(&f), sizeof(float));
111 };
112 auto appendInt = [&vertexBuffer](qint32 i) {
113 vertexBuffer.append(reinterpret_cast<const char *>(&i), 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(handMeshData.vertexPositions[i]);
120 appendFloat(position.x());
121 appendFloat(position.y());
122 appendFloat(position.z());
123 minBounds.setX(qMin(minBounds.x(), position.x()));
124 maxBounds.setX(qMax(maxBounds.x(), position.x()));
125 minBounds.setY(qMin(minBounds.y(), position.y()));
126 maxBounds.setY(qMax(maxBounds.y(), position.y()));
127 minBounds.setZ(qMin(minBounds.z(), position.z()));
128 maxBounds.setZ(qMax(maxBounds.z(), 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(minBounds, 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(indexLength * sizeof(int16_t));
168 for (qsizetype i = 0; i < indexLength; ++i) {
169 const auto &index = handMeshData.indices[i];
170 indexBuffer.append(reinterpret_cast<const char *>(&index), 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";
183 }
184
185 m_instance = instance;
186 m_session = session;
187
188 setupHandTracking();
189
190 setupActions();
191
192 QXRHandComponentPath aClick = makeHandInputPaths("input/a/click"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
193 QXRHandComponentPath bClick = makeHandInputPaths("input/b/click"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
194 QXRHandComponentPath aTouch = makeHandInputPaths("input/a/touch"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
195 QXRHandComponentPath bTouch = makeHandInputPaths("input/b/touch"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
196
197 QXRHandComponentPath xClick = makeHandInputPaths("input/x/click"); // OCULUS_TOUCH (left)
198 QXRHandComponentPath yClick = makeHandInputPaths("input/y/click"); // OCULUS_TOUCH (left)
199 QXRHandComponentPath xTouch = makeHandInputPaths("input/x/touch"); // OCULUS_TOUCH (left)
200 QXRHandComponentPath yTouch = makeHandInputPaths("input/y/touch"); // OCULUS_TOUCH (left)
201
202 QXRHandComponentPath menuClick = makeHandInputPaths("input/menu/click"); // OCULUS_TOUCH (left) | MICROSOFT_MRM (right + left) | HTC_VIVE (right + left)
203 QXRHandComponentPath systemClick = makeHandInputPaths("input/system/click"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left) | HTC_VIVE (right + left)
204 QXRHandComponentPath systemTouch = makeHandInputPaths("input/system/touch"); // VALVE_INDEX (right + left)
205
206 QXRHandComponentPath squeezeValue = makeHandInputPaths("input/squeeze/value"); // right + left: OCULUS_TOUCH | VALVE_INDEX
207 QXRHandComponentPath squeezeForce = makeHandInputPaths("input/squeeze/force"); // right + left: VALVE_INDEX
208 QXRHandComponentPath squeezeClick = makeHandInputPaths("input/squeeze/click"); // right + left: MICROSOFT_MRM | HTC_VIVE
209
210 QXRHandComponentPath triggerValue = makeHandInputPaths("input/trigger/value"); // right + left: OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
211 QXRHandComponentPath triggerTouch = makeHandInputPaths("input/trigger/touch"); // right + left: OCULUS_TOUCH | VALVE_INDEX
212 QXRHandComponentPath triggerClick = makeHandInputPaths("input/trigger/click"); // right + left: VALVE_INDEX | HTC_VIVE
213
214 QXRHandComponentPath thumbstickX = makeHandInputPaths("input/thumbstick/x"); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
215 QXRHandComponentPath thumbstickY = makeHandInputPaths("input/thumbstick/y"); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
216 QXRHandComponentPath thumbstickClick = makeHandInputPaths("input/thumbstick/click"); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
217 QXRHandComponentPath thumbstickTouch = makeHandInputPaths("input/thumbstick/touch"); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left)
218 QXRHandComponentPath thumbrestTouch = makeHandInputPaths("input/thumbrest/touch"); // OCULUS_TOUCH (right + left)
219
220 QXRHandComponentPath trackpadX = makeHandInputPaths("input/trackpad/x"); // right + left: VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
221 QXRHandComponentPath trackpadY = makeHandInputPaths("input/trackpad/y"); // right + left: VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
222 QXRHandComponentPath trackpadForce = makeHandInputPaths("input/trackpad/force"); // right + left: VALVE_INDEX
223 QXRHandComponentPath trackpadClick = makeHandInputPaths("input/trackpad/click"); // right + left: VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
224 QXRHandComponentPath trackpadTouch = makeHandInputPaths("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(handLeftGripPose, "/user/hand/left/input/grip/pose");
237 setPath(handLeftAimPose, "/user/hand/left/input/aim/pose");
238 setPath(handLeftHaptic, "/user/hand/left/output/haptic");
239
240 setPath(handRightGripPose, "/user/hand/right/input/grip/pose");
241 setPath(handRightAimPose, "/user/hand/right/input/aim/pose");
242 setPath(handRightHaptic, "/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({ m_inputActions[actionId], path.paths[Hand::LeftHand] });
252 if (selector & RightHandSubPath)
253 bindings.push_back({ m_inputActions[actionId], 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(oculusTouchProfile, "/interaction_profiles/oculus/touch_controller");
282 std::vector<XrActionSuggestedBinding> bindings {{
283 {m_handActions.gripPoseAction, handLeftGripPose},
284 {m_handActions.aimPoseAction, handLeftAimPose},
285 {m_handActions.hapticAction, handLeftHaptic},
286
287 {m_handActions.gripPoseAction, handRightGripPose},
288 {m_handActions.aimPoseAction, handRightAimPose},
289 {m_handActions.hapticAction, 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(xrSuggestInteractionProfileBindings(m_instance, &suggestedBindings)))
300 qWarning("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(handInteractionProfile, "/interaction_profiles/microsoft/hand_interaction");
308 std::vector<XrActionSuggestedBinding> bindings {{
309 {m_handActions.gripPoseAction, handLeftGripPose},
310 {m_handActions.aimPoseAction, handLeftAimPose}, // ### Binding succeeds, but does not seem to work on the Quest 3
311 {m_handActions.gripPoseAction, handRightGripPose},
312 {m_handActions.aimPoseAction, 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(xrSuggestInteractionProfileBindings(m_instance, &suggestedBindings)))
328 qWarning("Failed to get suggested interaction profile bindings for MSFT hand interaction");
329 }
330
331 {
332 XrPath htcViveProfile;
333 setPath(htcViveProfile, "/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 {m_handActions.gripPoseAction, handLeftGripPose},
349 {m_handActions.aimPoseAction, handLeftAimPose},
350 {m_handActions.hapticAction, handLeftHaptic},
351
352 {m_handActions.gripPoseAction, handRightGripPose},
353 {m_handActions.aimPoseAction, handRightAimPose},
354 {m_handActions.hapticAction, 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(xrSuggestInteractionProfileBindings(m_instance, &suggestedBindings)))
365 qWarning("Failed to get suggested interaction profile bindings for Vive controller");
366 }
367
368 // Microsoft MRM ### TODO
369 {
370 XrPath microsoftMotionProfile;
371 setPath(microsoftMotionProfile, "/interaction_profiles/microsoft/motion_controller");
372 }
373
374 // Valve Index ### TODO
375 {
376 XrPath valveIndexProfile;
377 setPath(valveIndexProfile, "/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(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_handGripSpace[0])))
389 qWarning("Failed to create action space for handGripSpace[0]");
390 actionSpaceInfo.subactionPath = m_handSubactionPath[1];
391 if (!checkXrResult(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_handGripSpace[1])))
392 qWarning("Failed to create action space for handGripSpace[1]");
393
394 actionSpaceInfo.action = m_handActions.aimPoseAction;
395 actionSpaceInfo.subactionPath = m_handSubactionPath[0];
396 if (!checkXrResult(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_handAimSpace[0])))
397 qWarning("Failed to create action space for handAimSpace[0]");
398 actionSpaceInfo.subactionPath = m_handSubactionPath[1];
399 if (!checkXrResult(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_handAimSpace[1])))
400 qWarning("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(xrAttachSessionActionSets(m_session, &attachInfo)))
409 qWarning("Failed to attach action sets to session");
410
411 m_initialized = true;
412}
413
415{
416 if (!m_initialized)
417 return;
418
419 m_initialized = false;
420
421 xrDestroySpace(m_handGripSpace[0]);
422 xrDestroySpace(m_handGripSpace[1]);
423 xrDestroySpace(m_handAimSpace[0]);
424 xrDestroySpace(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
438{
439 QSSG_ASSERT(inputManager != nullptr, return nullptr);
440 return inputManager->d_func();
441}
442
444{
445 if (!m_initialized)
446 return;
447
448 // Sync Actions
449 const XrActiveActionSet activeActionSet{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(m_session, &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("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(xrGetActionStateBoolean(m_session, &getInfo, &boolValue))) {
480 if (boolValue.isActive && boolValue.changedSinceLastSync) {
481 //qDebug() << "ACTION" << i << def.shortName << bool(boolValue.currentState);
482 setInputValue(hand, def.id, def.shortName, float(boolValue.currentState));
483 }
484 } else {
485 qWarning("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(xrGetActionStateFloat(m_session, &getInfo, &floatValue))) {
493 if (floatValue.isActive && floatValue.changedSinceLastSync) {
494 //qDebug() << "ACTION" << i << def.shortName << floatValue.currentState;
495 setInputValue(hand, def.id, def.shortName, float(floatValue.currentState));
496 }
497 } else {
498 qWarning("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(xrGetActionStatePose(m_session, &getInfo, &poseState)))
515 inputState->setIsActive(poseState.isActive);
516 else
517 qWarning("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(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*>(hapticFeedback->hapticEffect())) {
529 XrHapticVibration vibration {XR_TYPE_HAPTIC_VIBRATION, nullptr, 0, 0, 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 {XR_TYPE_HAPTIC_ACTION_INFO, nullptr, m_handActions.hapticAction, m_handSubactionPath[hand]};
535
536 if (!checkXrResult(xrApplyHapticFeedback(m_session, &hapticActionInfo, (const XrHapticBaseHeader*)&vibration))) {
537 qWarning("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(handSpace(hand, poseSpace), appSpace, predictedDisplayTime, &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 QVector3D(spaceLocation.pose.position.x,
571 spaceLocation.pose.position.y,
572 spaceLocation.pose.position.z) * 100.0f,
573 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(xrLocateHandJointsEXT_(handTracker[hand], &locateInfo[hand], &locations[hand])))
619 qWarning("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(OpenXRHelpers::toQVector(pose.position));
628 jr.append(OpenXRHelpers::toQQuaternion(pose.orientation));
629 }
630 m_handInputState[hand]->setJointPositionsAndRotations(jp, 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, name, 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, HandPoseSpace::AimPose) && !m_validAimStateFromUpdatePoses[hand]) {
655 if ((aimState[hand].status & XR_HAND_TRACKING_AIM_VALID_BIT_FB)) {
656 setPosePositionAndRotation(hand, HandPoseSpace::AimPose,
657 QVector3D(aimState[hand].aimPose.position.x,
658 aimState[hand].aimPose.position.y,
659 aimState[hand].aimPose.position.z) * 100.0f,
660 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 OpenXRHelpers::resolveXrFunction(
675 m_instance,
676 "xrCreateHandTrackerEXT",
677 (PFN_xrVoidFunction*)(&xrCreateHandTrackerEXT_));
678 OpenXRHelpers::resolveXrFunction(
679 m_instance,
680 "xrDestroyHandTrackerEXT",
681 (PFN_xrVoidFunction*)(&xrDestroyHandTrackerEXT_));
682 OpenXRHelpers::resolveXrFunction(
683 m_instance,
684 "xrLocateHandJointsEXT",
685 (PFN_xrVoidFunction*)(&xrLocateHandJointsEXT_));
686 OpenXRHelpers::resolveXrFunction(
687 m_instance,
688 "xrGetHandMeshFB",
689 (PFN_xrVoidFunction*)(&xrGetHandMeshFB_));
690
691 if (xrCreateHandTrackerEXT_) {
692 XrHandTrackerCreateInfoEXT createInfo{};
693 createInfo.type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT;
694 createInfo.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT;
695 createInfo.hand = XR_HAND_LEFT_EXT;
696 if (!checkXrResult(xrCreateHandTrackerEXT_(m_session, &createInfo, &handTracker[QtQuick3DXr::LeftHand])))
697 qWarning("Failed to create left hand tracker");
698 createInfo.hand = XR_HAND_RIGHT_EXT;
699 if (!checkXrResult(xrCreateHandTrackerEXT_(m_session, &createInfo, &handTracker[QtQuick3DXr::RightHand])))
700 qWarning("Failed to create right hand tracker");
701 }
702 if (xrGetHandMeshFB_) {
703 for (auto hand : {Hand::LeftHand, Hand::RightHand}) {
704 if (queryHandMesh(hand))
705 createHandModelData(hand);
706 }
707 }
708}
709
710bool QQuick3DXrInputManagerPrivate::queryHandMesh(Hand hand)
711{
712 XrHandTrackingMeshFB mesh {};
713 mesh.type = XR_TYPE_HAND_TRACKING_MESH_FB;
714 // Left hand
715 if (!checkXrResult(xrGetHandMeshFB_(handTracker[hand], &mesh))) {
716 qWarning("Failed to query hand mesh info.");
717 return false;
718 }
719
720 mesh.jointCapacityInput = mesh.jointCountOutput;
721 mesh.vertexCapacityInput = mesh.vertexCountOutput;
722 mesh.indexCapacityInput = mesh.indexCountOutput;
723 m_handMeshData[hand].vertexPositions.resize(mesh.vertexCapacityInput);
724 m_handMeshData[hand].vertexNormals.resize(mesh.vertexCapacityInput);
725 m_handMeshData[hand].vertexUVs.resize(mesh.vertexCapacityInput);
726 m_handMeshData[hand].vertexBlendIndices.resize(mesh.vertexCapacityInput);
727 m_handMeshData[hand].vertexBlendWeights.resize(mesh.vertexCapacityInput);
728 m_handMeshData[hand].indices.resize(mesh.indexCapacityInput);
729 mesh.jointBindPoses = m_handMeshData[hand].jointBindPoses;
730 mesh.jointParents = m_handMeshData[hand].jointParents;
731 mesh.jointRadii = m_handMeshData[hand].jointRadii;
732 mesh.vertexPositions = m_handMeshData[hand].vertexPositions.data();
733 mesh.vertexNormals = m_handMeshData[hand].vertexNormals.data();
734 mesh.vertexUVs = m_handMeshData[hand].vertexUVs.data();
735 mesh.vertexBlendIndices = m_handMeshData[hand].vertexBlendIndices.data();
736 mesh.vertexBlendWeights = m_handMeshData[hand].vertexBlendWeights.data();
737 mesh.indices = m_handMeshData[hand].indices.data();
738
739 if (!checkXrResult(xrGetHandMeshFB_(handTracker[hand], &mesh))) {
740 qWarning("Failed to get hand mesh data.");
741 return false;
742 }
743
744 return true;
745};
746
747void QQuick3DXrInputManagerPrivate::setupActions()
748{
749 m_handInputActionDefs = {
750 { QQuick3DXrInputAction::Button1Pressed, "b1_pressed", "Button 1 Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
751 { QQuick3DXrInputAction::Button1Touched, "b1_touched", "Button 1 Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
752 { QQuick3DXrInputAction::Button2Pressed, "b2_pressed", "Button 2 Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
753 { QQuick3DXrInputAction::Button2Touched, "b2_touched", "Button 2 Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
754 { QQuick3DXrInputAction::ButtonMenuPressed, "bmenu_pressed", "Button Menu Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
755 { QQuick3DXrInputAction::ButtonMenuTouched, "bmenu_touched", "Button Menu Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
756 { QQuick3DXrInputAction::ButtonSystemPressed, "bsystem_pressed", "Button System Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
757 { QQuick3DXrInputAction::ButtonSystemTouched, "bsystem_touched", "Button System Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
758 { QQuick3DXrInputAction::SqueezeValue, "squeeze_value", "Squeeze Value", XR_ACTION_TYPE_FLOAT_INPUT },
759 { QQuick3DXrInputAction::SqueezeForce, "squeeze_force", "Squeeze Force", XR_ACTION_TYPE_FLOAT_INPUT },
760 { QQuick3DXrInputAction::SqueezePressed, "squeeze_pressed", "Squeeze Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
761 { QQuick3DXrInputAction::TriggerValue, "trigger_value", "Trigger Value", XR_ACTION_TYPE_FLOAT_INPUT },
762 { QQuick3DXrInputAction::TriggerPressed, "trigger_pressed", "Trigger Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
763 { QQuick3DXrInputAction::TriggerTouched, "trigger_touched", "Trigger Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
764 { QQuick3DXrInputAction::ThumbstickX, "thumbstick_x", "Thumbstick X", XR_ACTION_TYPE_FLOAT_INPUT },
765 { QQuick3DXrInputAction::ThumbstickY, "thumbstick_y", "Thumbstick Y", XR_ACTION_TYPE_FLOAT_INPUT },
766 { QQuick3DXrInputAction::ThumbstickPressed, "thumbstick_pressed", "Thumbstick Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
767 { QQuick3DXrInputAction::ThumbstickTouched, "thumbstick_touched", "Thumbstick Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
768 { QQuick3DXrInputAction::ThumbrestTouched, "thumbrest_touched", "Thumbrest Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
769 { QQuick3DXrInputAction::TrackpadX, "trackpad_x", "Trackpad X", XR_ACTION_TYPE_FLOAT_INPUT },
770 { QQuick3DXrInputAction::TrackpadY, "trackpad_y", "Trackpad Y", XR_ACTION_TYPE_FLOAT_INPUT },
771 { QQuick3DXrInputAction::TrackpadForce, "trackpad_force", "Trackpad Force", XR_ACTION_TYPE_FLOAT_INPUT },
772 { QQuick3DXrInputAction::TrackpadTouched, "trackpad_touched", "Trackpad Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
773 { QQuick3DXrInputAction::TrackpadPressed, "trackpad_pressed", "Trackpad Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT }
774 };
775
776 // Create an action set.
777 {
778 XrActionSetCreateInfo actionSetInfo{};
779 actionSetInfo.type = XR_TYPE_ACTION_SET_CREATE_INFO;
780 strcpy(actionSetInfo.actionSetName, "gameplay");
781 strcpy(actionSetInfo.localizedActionSetName, "Gameplay");
782 actionSetInfo.priority = 0;
783 if (!checkXrResult(xrCreateActionSet(m_instance, &actionSetInfo, &m_actionSet)))
784 qWarning("Failed to create gameplay action set");
785 }
786
787 // Create Hand Actions
788 setPath(m_handSubactionPath[0], "/user/hand/left");
789 setPath(m_handSubactionPath[1], "/user/hand/right");
790
791 for (const auto &def : m_handInputActionDefs) {
792 createAction(def.type,
793 def.shortName,
794 def.localizedName,
795 2,
796 m_handSubactionPath,
797 m_inputActions[def.id]);
798 }
799
800 createAction(XR_ACTION_TYPE_VIBRATION_OUTPUT,
801 "vibrate_hand",
802 "Vibrate Hand",
803 2,
804 m_handSubactionPath,
805 m_handActions.hapticAction);
806 createAction(XR_ACTION_TYPE_POSE_INPUT,
807 "hand_grip_pose",
808 "Hand Grip Pose",
809 2,
810 m_handSubactionPath,
811 m_handActions.gripPoseAction);
812 createAction(XR_ACTION_TYPE_POSE_INPUT,
813 "hand_aim_pose",
814 "Hand Aim Pose",
815 2,
816 m_handSubactionPath,
817 m_handActions.aimPoseAction);
818
819}
820
821void QQuick3DXrInputManagerPrivate::destroyActions()
822{
823 for (auto &action : m_inputActions) {
824 if (action)
825 xrDestroyAction(action);
826 }
827
828 xrDestroyAction(m_handActions.gripPoseAction);
829 xrDestroyAction(m_handActions.aimPoseAction);
830 xrDestroyAction(m_handActions.hapticAction);
831
832 xrDestroyActionSet(m_actionSet);
833}
834
835bool QQuick3DXrInputManagerPrivate::checkXrResult(const XrResult &result)
836{
837 return OpenXRHelpers::checkXrResult(result, m_instance);
838}
839
840void QQuick3DXrInputManagerPrivate::setPath(XrPath &path, const QByteArray &pathString)
841{
842 if (!checkXrResult(xrStringToPath(m_instance, pathString.constData(), &path)))
843 qWarning("xrStringToPath failed");
844}
845
846void QQuick3DXrInputManagerPrivate::createAction(XrActionType type,
847 const char *name,
848 const char *localizedName,
849 int numSubactions,
850 XrPath *subactionPath,
851 XrAction &action)
852{
853 XrActionCreateInfo actionInfo{};
854 actionInfo.type = XR_TYPE_ACTION_CREATE_INFO;
855 actionInfo.actionType = type;
856 strcpy(actionInfo.actionName, name);
857 strcpy(actionInfo.localizedActionName, localizedName);
858 actionInfo.countSubactionPaths = quint32(numSubactions);
859 actionInfo.subactionPaths = subactionPath;
860 if (!checkXrResult(xrCreateAction(m_actionSet, &actionInfo, &action)))
861 qCDebug(lcQuick3DXr) << "xrCreateAction failed. Name:" << name << "localizedName:" << localizedName;
862}
863
864void QQuick3DXrInputManagerPrivate::getBoolInputState(XrActionStateGetInfo &getInfo, const XrAction &action, std::function<void(bool)> setter)
865{
866 getInfo.action = action;
867 XrActionStateBoolean boolValue{};
868 boolValue.type = XR_TYPE_ACTION_STATE_BOOLEAN;
869 if (checkXrResult(xrGetActionStateBoolean(m_session, &getInfo, &boolValue))) {
870 if (boolValue.isActive == XR_TRUE)
871 setter(bool(boolValue.currentState));
872 } else {
873 qWarning("Failed to get action state: bool");
874 }
875}
876
877void QQuick3DXrInputManagerPrivate::getFloatInputState(XrActionStateGetInfo &getInfo, const XrAction &action, std::function<void(float)> setter)
878{
879 getInfo.action = action;
880 XrActionStateFloat floatValue{};
881 floatValue.type = XR_TYPE_ACTION_STATE_FLOAT;
882 if (checkXrResult(xrGetActionStateFloat(m_session, &getInfo, &floatValue))) {
883 if (floatValue.isActive == XR_TRUE)
884 setter(float(floatValue.currentState));
885 } else {
886 qWarning("Failed to get action state: float");
887 }
888}
889
890XrSpace QQuick3DXrInputManagerPrivate::handSpace(QQuick3DXrInputManagerPrivate::Hand hand, HandPoseSpace poseSpace)
891{
892 if (poseSpace == HandPoseSpace::GripPose)
893 return m_handGripSpace[hand];
894 else
895 return m_handAimSpace[hand];
896}
897
898bool QQuick3DXrInputManagerPrivate::isHandActive(QQuick3DXrInputManagerPrivate::Hand hand)
899{
900 return m_handInputState[hand]->isActive();
901}
902
904{
905 return m_handInputState[hand]->isHandTrackingActive();
906}
907
908void QQuick3DXrInputManagerPrivate::setPosePositionAndRotation(Hand hand, HandPoseSpace poseSpace, const QVector3D &position, const QQuaternion &rotation)
909{
910 for (auto *controller : std::as_const(m_controllers)) {
911 if (QtQuick3DXr::handForController(controller->controller()) == hand && QtQuick3DXr::pose_cast(controller->poseSpace()) == poseSpace) {
912 controller->setPosition(position);
913 controller->setRotation(rotation);
914 }
915 }
916}
917
918void QQuick3DXrInputManagerPrivate::setInputValue(Hand hand, int id, const char *shortName, float value)
919{
920 QSSG_ASSERT(hand < 2, hand = Hand::LeftHand);
921 QQuick3DXrActionMapper::handleInput(QQuick3DXrInputAction::Action(id), static_cast<QQuick3DXrInputAction::Controller>(hand), shortName, value);
922}
923
925{
926 return m_handInputState[Hand::LeftHand];
927}
928
930{
931 return m_handInputState[Hand::RightHand];
932}
933
934static inline QMatrix4x4 transformMatrix(const QVector3D &position, const QQuaternion &rotation)
935{
936 QMatrix4x4 transform = QMatrix4x4{rotation.toRotationMatrix()};
937
938 transform(0, 3) += position[0];
939 transform(1, 3) += position[1];
940 transform(2, 3) += position[2];
941
942 return transform;
943}
944
945void QQuick3DXrInputManagerPrivate::setupHandModelInternal(QQuick3DXrHandModel *model, Hand hand)
946{
947 QQuick3DGeometry *geometry = m_handGeometryData[hand].geometry;
948 if (!geometry)
949 return;
950
951 model->setGeometry(geometry);
952
953 QQuick3DSkin *skin = new QQuick3DSkin(model);
954 auto jointListProp = skin->joints();
955 QList<QMatrix4x4> inverseBindPoses;
956 inverseBindPoses.reserve(XR_HAND_JOINT_COUNT_EXT);
957
958 const auto &handMeshData = m_handMeshData[hand];
959
960 for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; ++i) {
961 const auto &pose = handMeshData.jointBindPoses[i];
962 const QVector3D pos = OpenXRHelpers::toQVector(pose.position);
963 const QQuaternion rot = OpenXRHelpers::toQQuaternion(pose.orientation);
964 inverseBindPoses.append(transformMatrix(pos, rot).inverted());
965 QQuick3DNode *joint = new QQuick3DNode(model);
966 joint->setPosition(pos);
967 joint->setRotation(rot);
968 jointListProp.append(&jointListProp, joint);
969 }
970 skin->setInverseBindPoses(inverseBindPoses);
971 model->setSkin(skin);
972}
973
975{
976 QSSG_ASSERT(model != nullptr, return);
977
978 if (model->geometry() != nullptr || model->skin() != nullptr) {
979 qWarning() << "Hand model already has geometry or skin set.";
980 return;
981 }
982
983 auto hand = model->hand();
984 if (hand == QQuick3DXrHandModel::LeftHand)
985 setupHandModelInternal(model, Hand::LeftHand);
986 else if (hand == QQuick3DXrHandModel::RightHand)
987 setupHandModelInternal(model, Hand::RightHand);
988 else
989 qWarning() << "No matching hand tracker input found for hand model.";
990}
991
992// Used both to add a new controller, and notify that an existing one has changed
993void QQuick3DXrInputManagerPrivate::registerController(QQuick3DXrController *controller)
994{
995 m_poseUsageDirty = true;
996 if (controller->controller() == QQuick3DXrController::ControllerNone) {
997 m_controllers.remove(controller);
998 return;
999 }
1000 // No point in checking whether it's already in the set: that's just as expensive as inserting
1001 m_controllers.insert(controller);
1002}
1003
1004void QQuick3DXrInputManagerPrivate::unregisterController(QQuick3DXrController *controller)
1005{
1006 m_poseUsageDirty = m_controllers.remove(controller);
1007}
1008
1009bool QQuick3DXrInputManagerPrivate::isPoseInUse(Hand hand, HandPoseSpace poseSpace)
1010{
1011 QSSG_ASSERT(uint(hand) < 2 && uint(poseSpace) < 2, return false);
1012 if (m_poseUsageDirty) {
1013 std::fill_n(&m_poseInUse[0][0], 4, false);
1014 for (const auto *controller : std::as_const(m_controllers)) {
1015 m_poseInUse[uint(controller->controller())][uint(controller->poseSpace())] = true;
1016 }
1017 m_poseUsageDirty = false;
1018 }
1019 return m_poseInUse[uint(hand)][uint(poseSpace)];
1020}
1021
1022void QQuick3DXrInputManagerPrivate::createHandModelData(Hand hand)
1023{
1024 const auto &handMeshData = m_handMeshData[hand];
1025
1026 auto &geometry = m_handGeometryData[hand].geometry;
1027 delete geometry;
1028 geometry = createHandMeshGeometry(handMeshData);
1029}
1030
1031QT_END_NAMESPACE
void init(XrInstance instance, XrSession session)
bool isPoseInUse(Hand hand, HandPoseSpace poseSpace)
void updatePoses(XrTime predictedDisplayTime, XrSpace appSpace)
void setPosePositionAndRotation(Hand hand, HandPoseSpace poseSpace, const QVector3D &position, const QQuaternion &rotation)
QQuick3DXrHandInput * rightHandInput() const
static QQuick3DXrInputManagerPrivate * get(QQuick3DXrInputManager *inputManager)
void updateHandtracking(XrTime predictedDisplayTime, XrSpace appSpace, bool aimExtensionEnabled)
QQuick3DXrInputManagerPrivate(QQuick3DXrInputManager &manager)
void setupHandModel(QQuick3DXrHandModel *model)
void registerController(QQuick3DXrController *controller)
QQuick3DXrHandInput * leftHandInput() const
void unregisterController(QQuick3DXrController *controller)
XrSpace handSpace(Hand hand, HandPoseSpace poseSpace)
static QMatrix4x4 transformMatrix(const QVector3D &position, const QQuaternion &rotation)