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