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