Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qopenxrinputmanager.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
5#include "qopenxrhelpers_p.h"
9
10#include "qopenxrcontroller_p.h" //### InputAction enum
11
12#include <QDebug>
13
14#include <private/qquick3djoint_p.h>
15
17
18QOpenXRInputManager::QOpenXRInputManager()
19{
20 m_handInputState[QOpenXRInputManager::LeftHand] = new QOpenXRHandInput(this);
21 m_handInputState[QOpenXRInputManager::RightHand] = new QOpenXRHandInput(this);
22 m_handTrackerInputState[QOpenXRInputManager::LeftHand] = new QOpenXRHandTrackerInput(this);
23 m_handTrackerInputState[QOpenXRInputManager::RightHand] = new QOpenXRHandTrackerInput(this);
24 m_gamepadInputState = new QOpenXRGamepadInput(this);
25}
26
27QOpenXRInputManager::~QOpenXRInputManager()
28{
29 teardown();
30 delete m_handInputState[QOpenXRInputManager::LeftHand];
31 delete m_handInputState[QOpenXRInputManager::RightHand];
32 delete m_handTrackerInputState[QOpenXRInputManager::LeftHand];
33 delete m_handTrackerInputState[QOpenXRInputManager::RightHand];
34 delete m_gamepadInputState;
35
36 m_handInputState[QOpenXRInputManager::LeftHand] = nullptr;
37 m_handInputState[QOpenXRInputManager::RightHand] = nullptr;
38 m_handTrackerInputState[QOpenXRInputManager::LeftHand] = nullptr;
39 m_handTrackerInputState[QOpenXRInputManager::RightHand] = nullptr;
40 m_gamepadInputState = nullptr;
41}
42
48
49QOpenXRInputManager::QXRHandComponentPath QOpenXRInputManager::makeHandInputPaths(const QByteArrayView path)
50{
51 QXRHandComponentPath res;
52 setPath(res.paths[QOpenXRInputManager::LeftHand], "/user/hand/left/" + path);
53 setPath(res.paths[QOpenXRInputManager::RightHand], "/user/hand/right/" + path);
54 return res;
55}
56
57
58XrPath QOpenXRInputManager::makeInputPath(const QByteArrayView path)
59{
60 XrPath res;
61 setPath(res, path.toByteArray());
62 return res;
63}
64
65QQuick3DGeometry *QOpenXRInputManager::createHandMeshGeometry(const HandMeshData &handMeshData)
66{
67 QQuick3DGeometry *geometry = new QQuick3DGeometry();
69
70 // Figure out which attributes should be used
71 const qsizetype expectedLength = handMeshData.vertexPositions.size();
72 bool hasPositions = !handMeshData.vertexPositions.isEmpty();
73 bool hasNormals = handMeshData.vertexNormals.size() >= expectedLength;
74 bool hasUV0s = handMeshData.vertexUVs.size() >= expectedLength;
75 bool hasJoints = handMeshData.vertexBlendIndices.size() >= expectedLength;
76 bool hasWeights = handMeshData.vertexBlendWeights.size() >= expectedLength;
77 bool hasIndexes = !handMeshData.indices.isEmpty();
78
79 int offset = 0;
80 if (hasPositions) {
82 offset += 3 * sizeof(float);
83 }
84
85 if (hasNormals) {
87 offset += 3 * sizeof(float);
88 }
89
90 if (hasUV0s) {
92 offset += 2 * sizeof(float);
93 }
94
95 if (hasJoints) {
97 offset += 4 * sizeof(qint32);
98 }
99
100 if (hasWeights) {
102 offset += 4 * sizeof(float);
103 }
104
105 if (hasIndexes)
107
108 // set up the vertex buffer
109 const int stride = offset;
110 const qsizetype bufferSize = expectedLength * stride;
111 geometry->setStride(stride);
112
113 QByteArray vertexBuffer;
114 vertexBuffer.reserve(bufferSize);
115
116 QVector3D minBounds;
117 QVector3D maxBounds;
118
119 auto appendFloat = [&vertexBuffer](float f) {
120 vertexBuffer.append(reinterpret_cast<const char *>(&f), sizeof(float));
121 };
122 auto appendInt = [&vertexBuffer](qint32 i) {
123 vertexBuffer.append(reinterpret_cast<const char *>(&i), sizeof(qint32));
124 };
125
126 for (qsizetype i = 0; i < expectedLength; ++i) {
127 // start writing float values to vertexBuffer
128 if (hasPositions) {
129 const QVector3D position = OpenXRHelpers::toQVector(handMeshData.vertexPositions[i]);
130 appendFloat(position.x());
131 appendFloat(position.y());
132 appendFloat(position.z());
133 minBounds.setX(qMin(minBounds.x(), position.x()));
134 maxBounds.setX(qMax(maxBounds.x(), position.x()));
135 minBounds.setY(qMin(minBounds.y(), position.y()));
136 maxBounds.setY(qMax(maxBounds.y(), position.y()));
137 minBounds.setZ(qMin(minBounds.z(), position.z()));
138 maxBounds.setZ(qMax(maxBounds.z(), position.z()));
139 }
140 if (hasNormals) {
141 const auto &normal = handMeshData.vertexNormals[i];
142 appendFloat(normal.x);
143 appendFloat(normal.y);
144 appendFloat(normal.z);
145 }
146
147 if (hasUV0s) {
148 const auto &uv0 = handMeshData.vertexUVs[i];
149 appendFloat(uv0.x);
150 appendFloat(uv0.y);
151 }
152
153 if (hasJoints) {
154 const auto &joint = handMeshData.vertexBlendIndices[i];
155 appendInt(joint.x);
156 appendInt(joint.y);
157 appendInt(joint.z);
158 appendInt(joint.w);
159 }
160
161 if (hasWeights) {
162 const auto &weight = handMeshData.vertexBlendWeights[i];
163 appendFloat(weight.x);
164 appendFloat(weight.y);
165 appendFloat(weight.z);
166 appendFloat(weight.w);
167 }
168 }
169
170 geometry->setBounds(minBounds, maxBounds);
171 geometry->setVertexData(vertexBuffer);
172
173 // Index Buffer
174 if (hasIndexes) {
175 const qsizetype indexLength = handMeshData.indices.size();
176 QByteArray indexBuffer;
177 indexBuffer.reserve(indexLength * sizeof(int16_t));
178 for (qsizetype i = 0; i < indexLength; ++i) {
179 const auto &index = handMeshData.indices[i];
180 indexBuffer.append(reinterpret_cast<const char *>(&index), sizeof(int16_t));
181 }
182 geometry->setIndexData(indexBuffer);
183 }
184
185 return geometry;
186}
187
188void QOpenXRInputManager::init(XrInstance instance, XrSession session)
189{
190 if (m_initialized) {
191 qWarning() << "QOpenXRInputManager: Trying to initialize an already initialized session";
192 teardown();
193 }
194
195 m_instance = instance;
196 m_session = session;
197
198 m_disableGamepad = false;
199
200 setupHandTracking();
201
202 // Gamepad actions lead to endless XR_ERROR_RUNTIME_FAILURE in
203 // xrSyncActions with the Meta XR Simulator (v57 at least). The Simulator
204 // can use an XBox controller to simulate head/controller/hand input (and
205 // not as a gamepad), so it is not clear if it is supposed to have gamepad
206 // support to begin with. So just disable it all.
207 XrInstanceProperties instanceProperties = {};
208 instanceProperties.type = XR_TYPE_INSTANCE_PROPERTIES;
209 if (xrGetInstanceProperties(m_instance, &instanceProperties) == XR_SUCCESS) {
210 if (strstr(instanceProperties.runtimeName, "Meta XR Simulator")) {
211 qDebug("QOpenXRInputManager: Disabling gamepad actions due to running on the Simulator");
212 m_disableGamepad = true;
213 }
214 }
215
216 // Also on Android. ### Why?
217#ifdef XR_USE_PLATFORM_ANDROID
218 qDebug("QOpenXRInputManager: Disabling gamepad actions due to running on Android");
219 m_disableGamepad = true;
220#endif
221
222 setupActions();
223
224 QXRHandComponentPath aClick = makeHandInputPaths("input/a/click"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
225 QXRHandComponentPath bClick = makeHandInputPaths("input/b/click"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
226 QXRHandComponentPath aTouch = makeHandInputPaths("input/a/touch"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
227 QXRHandComponentPath bTouch = makeHandInputPaths("input/b/touch"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
228
229 QXRHandComponentPath xClick = makeHandInputPaths("input/x/click"); // OCULUS_TOUCH (left)
230 QXRHandComponentPath yClick = makeHandInputPaths("input/y/click"); // OCULUS_TOUCH (left)
231 QXRHandComponentPath xTouch = makeHandInputPaths("input/x/touch"); // OCULUS_TOUCH (left)
232 QXRHandComponentPath yTouch = makeHandInputPaths("input/y/touch"); // OCULUS_TOUCH (left)
233
234 QXRHandComponentPath menuClick = makeHandInputPaths("input/menu/click"); // OCULUS_TOUCH (left) | MICROSOFT_MRM (right + left) | HTC_VIVE (right + left)
235 QXRHandComponentPath systemClick = makeHandInputPaths("input/system/click"); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left) | HTC_VIVE (right + left)
236 QXRHandComponentPath systemTouch = makeHandInputPaths("input/system/touch"); // VALVE_INDEX (right + left)
237
238 QXRHandComponentPath squeezeValue = makeHandInputPaths("input/squeeze/value"); // right + left: OCULUS_TOUCH | VALVE_INDEX
239 QXRHandComponentPath squeezeForce = makeHandInputPaths("input/squeeze/force"); // right + left: VALVE_INDEX
240 QXRHandComponentPath squeezeClick = makeHandInputPaths("input/squeeze/click"); // right + left: MICROSOFT_MRM | HTC_VIVE
241
242 QXRHandComponentPath triggerValue = makeHandInputPaths("input/trigger/value"); // right + left: OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
243 QXRHandComponentPath triggerTouch = makeHandInputPaths("input/trigger/touch"); // right + left: OCULUS_TOUCH | VALVE_INDEX
244 QXRHandComponentPath triggerClick = makeHandInputPaths("input/trigger/click"); // right + left: VALVE_INDEX | HTC_VIVE
245
246 QXRHandComponentPath thumbstickX = makeHandInputPaths("input/thumbstick/x"); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
247 QXRHandComponentPath thumbstickY = makeHandInputPaths("input/thumbstick/y"); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
248 QXRHandComponentPath thumbstickClick = makeHandInputPaths("input/thumbstick/click"); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
249 QXRHandComponentPath thumbstickTouch = makeHandInputPaths("input/thumbstick/touch"); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left)
250 QXRHandComponentPath thumbrestTouch = makeHandInputPaths("input/thumbrest/touch"); // OCULUS_TOUCH (right + left)
251
252 QXRHandComponentPath trackpadX = makeHandInputPaths("input/trackpad/x"); // right + left: VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
253 QXRHandComponentPath trackpadY = makeHandInputPaths("input/trackpad/y"); // right + left: VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
254 QXRHandComponentPath trackpadForce = makeHandInputPaths("input/trackpad/force"); // right + left: VALVE_INDEX
255 QXRHandComponentPath trackpadClick = makeHandInputPaths("input/trackpad/click"); // right + left: VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
256 QXRHandComponentPath trackpadTouch = makeHandInputPaths("input/trackpad/touch"); // right + left: MICROSOFT_MRM | HTC_VIVE
257
258 XrPath handLeftGripPose; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
259 XrPath handLeftAimPose; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
260 XrPath handLeftHaptic; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
261
262 XrPath handRightGripPose; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
263 XrPath handRightAimPose; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
264 XrPath handRightHaptic; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
265
266 XrPath gamepadMenuClick = makeInputPath("/user/gamepad/input/menu/click");
267 XrPath gamepadViewClick = makeInputPath("/user/gamepad/input/view/click");
268 XrPath gamepadAClick = makeInputPath("/user/gamepad/input/a/click");
269 XrPath gamepadBClick = makeInputPath("/user/gamepad/input/b/click");
270 XrPath gamepadXClick = makeInputPath("/user/gamepad/input/x/click");
271 XrPath gamepadYClick = makeInputPath("/user/gamepad/input/y/click");
272 XrPath gamepadDpadDownClick = makeInputPath("/user/gamepad/input/dpad_down/click");
273 XrPath gamepadDpadRightClick = makeInputPath("/user/gamepad/input/dpad_right/click");
274 XrPath gamepadDpadUpClick = makeInputPath("/user/gamepad/input/dpad_up/click");
275 XrPath gamepadDpadLeftClick = makeInputPath("/user/gamepad/input/dpad_left/click");
276 XrPath gamepadShoulderLeftClick = makeInputPath("/user/gamepad/input/shoulder_left/click");
277 XrPath gamepadShoulderRightClick = makeInputPath("/user/gamepad/input/shoulder_right/click");
278 XrPath gamepadThumbstickLeftClick = makeInputPath("/user/gamepad/input/thumbstick_left/click");
279 XrPath gamepadThumbstickRightClick = makeInputPath("/user/gamepad/input/thumbstick_right/click");
280 XrPath gamepadTriggerLeftValue = makeInputPath("/user/gamepad/input/trigger_left/value");
281 XrPath gamepadTriggerRightValue = makeInputPath("/user/gamepad/input/trigger_right/value");
282 XrPath gamepadThumbstickLeftX = makeInputPath("/user/gamepad/input/thumbstick_left/x");
283 XrPath gamepadThumbstickLeftY = makeInputPath("/user/gamepad/input/thumbstick_left/y");
284 XrPath gamepadThumbstickRightX = makeInputPath("/user/gamepad/input/thumbstick_right/x");
285 XrPath gamepadThumbstickRightY = makeInputPath("/user/gamepad/input/thumbstick_right/y");
286 XrPath gamepadHapticLeft = makeInputPath("/user/gamepad/output/haptic_left");
287 XrPath gamepadHapticRight = makeInputPath("/user/gamepad/output/haptic_right");
288 XrPath gamepadHapticLeftTrigger = makeInputPath("/user/gamepad/output/haptic_left_trigger");
289 XrPath gamepadHapticRightTrigger = makeInputPath("/user/gamepad/output/haptic_right_trigger");
290
291 // Hand Left
292
293 setPath(handLeftGripPose, "/user/hand/left/input/grip/pose");
294 setPath(handLeftAimPose, "/user/hand/left/input/aim/pose");
295 setPath(handLeftHaptic, "/user/hand/left/output/haptic");
296
297 setPath(handRightGripPose, "/user/hand/right/input/grip/pose");
298 setPath(handRightAimPose, "/user/hand/right/input/aim/pose");
299 setPath(handRightHaptic, "/user/hand/right/output/haptic");
300
301 // Bindings
302
303 using XrActionBindings = std::vector<XrActionSuggestedBinding>;
304 using HandInputMapping = std::vector<std::tuple<QOpenXRActionMapper::InputAction, QXRHandComponentPath, SubPathSelector>>;
305 using GamepadInputMapping = std::vector<std::tuple<QOpenXRActionMapper::InputAction, XrPath>>;
306 auto addToBindings = [this](XrActionBindings &bindings, const HandInputMapping &defs){
307 for (const auto &[actionId, path, selector] : defs) {
308 if (selector & LeftHandSubPath)
309 bindings.push_back({ m_inputActions[actionId], path.paths[LeftHand] });
310 if (selector & RightHandSubPath)
311 bindings.push_back({ m_inputActions[actionId], path.paths[RightHand] });
312 }
313 };
314
315 // Oculus Touch
316 {
317 HandInputMapping mappingDefs {
318 { QOpenXRActionMapper::Button1Pressed, xClick, LeftHandSubPath },
319 { QOpenXRActionMapper::Button1Pressed, aClick, RightHandSubPath },
320 { QOpenXRActionMapper::Button2Pressed, yClick, LeftHandSubPath },
321 { QOpenXRActionMapper::Button2Pressed, bClick, RightHandSubPath },
322 { QOpenXRActionMapper::Button1Touched, xTouch, LeftHandSubPath },
323 { QOpenXRActionMapper::Button1Touched, aTouch, RightHandSubPath },
324 { QOpenXRActionMapper::Button2Touched, yTouch, LeftHandSubPath },
325 { QOpenXRActionMapper::Button2Touched, bTouch, RightHandSubPath },
326 { QOpenXRActionMapper::ButtonMenuPressed, menuClick, LeftHandSubPath },
327 { QOpenXRActionMapper::ButtonSystemPressed, systemClick, RightHandSubPath },
328 { QOpenXRActionMapper::SqueezeValue, squeezeValue, BothHandsSubPath },
329 { QOpenXRActionMapper::TriggerValue, triggerValue, BothHandsSubPath },
330 { QOpenXRActionMapper::TriggerTouched, triggerTouch, BothHandsSubPath },
331 { QOpenXRActionMapper::ThumbstickX, thumbstickX, BothHandsSubPath },
332 { QOpenXRActionMapper::ThumbstickY, thumbstickY, BothHandsSubPath },
333 { QOpenXRActionMapper::ThumbstickPressed, thumbstickClick, BothHandsSubPath },
334 { QOpenXRActionMapper::ThumbstickTouched, thumbstickTouch, BothHandsSubPath },
335 { QOpenXRActionMapper::ThumbrestTouched, thumbrestTouch, BothHandsSubPath },
336 };
337
338 XrPath oculusTouchProfile;
339 setPath(oculusTouchProfile, "/interaction_profiles/oculus/touch_controller");
340 std::vector<XrActionSuggestedBinding> bindings {{
341 {m_handActions.gripPoseAction, handLeftGripPose},
342 {m_handActions.aimPoseAction, handLeftAimPose},
343 {m_handActions.hapticAction, handLeftHaptic},
344
345 {m_handActions.gripPoseAction, handRightGripPose},
346 {m_handActions.aimPoseAction, handRightAimPose},
347 {m_handActions.hapticAction, handRightHaptic},
348 }};
349
350 addToBindings(bindings, mappingDefs);
351
352 XrInteractionProfileSuggestedBinding suggestedBindings{};
353 suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
354 suggestedBindings.interactionProfile = oculusTouchProfile;
355 suggestedBindings.suggestedBindings = bindings.data();
356 suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size();
357 checkXrResult(xrSuggestInteractionProfileBindings(m_instance, &suggestedBindings), "suggested bindings: Oculus touch");
358 }
359
360 // Microsoft hand interaction extension as supported by Quest 3
361 // TODO: there are other, very similar, extensions: XR_HTC_HAND_INTERACTION_EXTENSION_NAME and XR_EXT_HAND_INTERACTION_EXTENSION_NAME
362 {
363 XrPath handInteractionProfile;
364 setPath(handInteractionProfile, "/interaction_profiles/microsoft/hand_interaction");
365 std::vector<XrActionSuggestedBinding> bindings {{
366 {m_handActions.gripPoseAction, handLeftGripPose},
367 {m_handActions.aimPoseAction, handLeftAimPose}, // ### Binding succeeds, but does not seem to work on the Quest 3
368 {m_handActions.gripPoseAction, handRightGripPose},
369 {m_handActions.aimPoseAction, handRightAimPose},
370 }};
371
372 HandInputMapping mappingDefs {
373 { QOpenXRActionMapper::SqueezeValue, squeezeValue, BothHandsSubPath },
374 };
375
376 addToBindings(bindings, mappingDefs);
377
378 XrInteractionProfileSuggestedBinding suggestedBindings{};
379 suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
380 suggestedBindings.interactionProfile = handInteractionProfile;
381 suggestedBindings.suggestedBindings = bindings.data();
382 suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size();
383
384 checkXrResult(xrSuggestInteractionProfileBindings(m_instance, &suggestedBindings), "suggested bindings: MSFT hand interaction");
385 }
386
387 {
388 XrPath htcViveProfile;
389 setPath(htcViveProfile, "/interaction_profiles/htc/vive_controller");
390
391 HandInputMapping mappingDefs {
392 { QOpenXRActionMapper::ButtonMenuPressed, menuClick, BothHandsSubPath },
393 { QOpenXRActionMapper::ButtonSystemPressed, systemClick, BothHandsSubPath },
394 { QOpenXRActionMapper::SqueezePressed, squeezeClick, BothHandsSubPath },
395 { QOpenXRActionMapper::TriggerValue, triggerValue, BothHandsSubPath },
396 { QOpenXRActionMapper::TriggerPressed, triggerClick, BothHandsSubPath },
397 { QOpenXRActionMapper::TrackpadX, trackpadX, BothHandsSubPath },
398 { QOpenXRActionMapper::TrackpadY, trackpadY, BothHandsSubPath },
399 { QOpenXRActionMapper::TrackpadPressed, trackpadClick, BothHandsSubPath },
400 { QOpenXRActionMapper::TrackpadTouched, trackpadTouch, BothHandsSubPath },
401 };
402
403 std::vector<XrActionSuggestedBinding> bindings {{
404 {m_handActions.gripPoseAction, handLeftGripPose},
405 {m_handActions.aimPoseAction, handLeftAimPose},
406 {m_handActions.hapticAction, handLeftHaptic},
407
408 {m_handActions.gripPoseAction, handRightGripPose},
409 {m_handActions.aimPoseAction, handRightAimPose},
410 {m_handActions.hapticAction, handRightHaptic},
411 }};
412
413 addToBindings(bindings, mappingDefs);
414
415 XrInteractionProfileSuggestedBinding suggestedBindings{};
416 suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
417 suggestedBindings.interactionProfile = htcViveProfile;
418 suggestedBindings.suggestedBindings = bindings.data();
419 suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size();
420 checkXrResult(xrSuggestInteractionProfileBindings(m_instance, &suggestedBindings), "suggested bindings: Vive controller");
421 }
422
423 // Microsoft MRM ### TODO
424 {
425 XrPath microsoftMotionProfile;
426 setPath(microsoftMotionProfile, "/interaction_profiles/microsoft/motion_controller");
427 }
428
429 // Valve Index ### TODO
430 {
431 XrPath valveIndexProfile;
432 setPath(valveIndexProfile, "/interaction_profiles/valve/index_controller");
433 }
434
435 // XBox Controller
436 if (!m_disableGamepad) {
437 XrPath xboxControllerProfile;
438 setPath(xboxControllerProfile, "/interaction_profiles/microsoft/xbox_controller");
439
440 GamepadInputMapping mappingDefs {
447 {QOpenXRActionMapper::GamepadButtonDownPressed, gamepadDpadDownClick},
448 {QOpenXRActionMapper::GamepadButtonRightPressed, gamepadDpadRightClick},
450 {QOpenXRActionMapper::GamepadButtonLeftPressed, gamepadDpadLeftClick},
451 {QOpenXRActionMapper::GamepadShoulderLeftPressed, gamepadShoulderLeftClick},
452 {QOpenXRActionMapper::GamepadShoulderRightPressed, gamepadShoulderRightClick},
453 {QOpenXRActionMapper::GamepadThumbstickLeftPressed, gamepadThumbstickLeftClick},
454 {QOpenXRActionMapper::GamepadThumbstickRightPressed, gamepadThumbstickRightClick},
455 {QOpenXRActionMapper::GamepadTriggerLeft, gamepadTriggerLeftValue},
456 {QOpenXRActionMapper::GamepadTriggerRight, gamepadTriggerRightValue},
457 {QOpenXRActionMapper::GamepadThumbstickLeftX, gamepadThumbstickLeftX},
458 {QOpenXRActionMapper::GamepadThumbstickLeftY, gamepadThumbstickLeftY},
459 {QOpenXRActionMapper::GamepadThumbstickRightX, gamepadThumbstickRightX},
460 {QOpenXRActionMapper::GamepadThumbstickRightY, gamepadThumbstickRightY},
461 };
462
463 std::vector<XrActionSuggestedBinding> bindings {{
464 {m_gamepadActions.hapticLeftAction, gamepadHapticLeft},
465 {m_gamepadActions.hapticRightAction, gamepadHapticRight},
466 {m_gamepadActions.hapticLeftTriggerAction, gamepadHapticLeftTrigger},
467 {m_gamepadActions.hapticRightTriggerAction, gamepadHapticRightTrigger},
468 }};
469
470 for (const auto &[actionId, path] : mappingDefs) {
471 bindings.push_back({ m_inputActions[actionId], path });
472 }
473
474 XrInteractionProfileSuggestedBinding suggestedBindings{};
475 suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
476 suggestedBindings.interactionProfile = xboxControllerProfile;
477 suggestedBindings.suggestedBindings = bindings.data();
478 suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size();
479 checkXrResult(xrSuggestInteractionProfileBindings(m_instance, &suggestedBindings), "suggested bindings: XBox controller");
480 }
481
482 // Setup Action Spaces
483
484 XrActionSpaceCreateInfo actionSpaceInfo{};
485 actionSpaceInfo.type = XR_TYPE_ACTION_SPACE_CREATE_INFO;
486 actionSpaceInfo.action = m_handActions.gripPoseAction;
487 actionSpaceInfo.poseInActionSpace.orientation.w = 1.0f;
488 //actionSpaceInfo.poseInActionSpace.orientation.y = 1.0f;
489 actionSpaceInfo.subactionPath = m_handSubactionPath[0];
490 checkXrResult(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_handGripSpace[0]), "action space: handGripSpace[0]");
491 actionSpaceInfo.subactionPath = m_handSubactionPath[1];
492 checkXrResult(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_handGripSpace[1]), "action space: handGripSpace[1]");
493
494 actionSpaceInfo.action = m_handActions.aimPoseAction;
495 actionSpaceInfo.subactionPath = m_handSubactionPath[0];
496 checkXrResult(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_handAimSpace[0]), "action space: handAimSpace[0]");
497 actionSpaceInfo.subactionPath = m_handSubactionPath[1];
498 checkXrResult(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_handAimSpace[1]), "action space: handAimSpace[1]");
499
500 // Attach Action set to session
501
502 XrSessionActionSetsAttachInfo attachInfo{};
503 attachInfo.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO;
504 attachInfo.countActionSets = 1;
505 attachInfo.actionSets = &m_actionSet;
506 checkXrResult(xrAttachSessionActionSets(m_session, &attachInfo), "attach action set");
507
508 m_initialized = true;
509}
510
512{
513 if (!m_initialized)
514 return;
515
516 m_initialized = false;
517
518 xrDestroySpace(m_handGripSpace[0]);
519 xrDestroySpace(m_handGripSpace[1]);
520 xrDestroySpace(m_handAimSpace[0]);
521 xrDestroySpace(m_handAimSpace[1]);
522
523 destroyActions();
524
528 }
529
530 m_instance = {XR_NULL_HANDLE};
531 m_session = {XR_NULL_HANDLE};
532}
533
535{
536 if (!m_initialized)
537 return;
538
539 // Sync Actions
540 const XrActiveActionSet activeActionSet{m_actionSet, XR_NULL_PATH};
541 XrActionsSyncInfo syncInfo{};
542 syncInfo.type = XR_TYPE_ACTIONS_SYNC_INFO;
543 syncInfo.countActiveActionSets = 1;
544 syncInfo.activeActionSets = &activeActionSet;
545 XrResult result = xrSyncActions(m_session, &syncInfo);
546 if (!(result == XR_SUCCESS ||
547 result == XR_SESSION_LOSS_PENDING ||
548 result == XR_SESSION_NOT_FOCUSED)) {
549 checkXrResult(result, "xrSyncActions");
550 return;
551 }
552
553 // Hands
554 XrActionStateGetInfo getInfo{};
555 getInfo.type = XR_TYPE_ACTION_STATE_GET_INFO;
556 for (int i = 0; i < 2; ++i) {
557
558 getInfo.subactionPath = m_handSubactionPath[i];
559 auto &inputState = m_handInputState[i];
560
561 for (const auto &def : m_handInputActionDefs) {
562 getInfo.action = m_inputActions[def.id];
563 switch (def.type) {
564 case XR_ACTION_TYPE_BOOLEAN_INPUT: {
565 XrActionStateBoolean boolValue{};
566 boolValue.type = XR_TYPE_ACTION_STATE_BOOLEAN;
567 checkXrResult(xrGetActionStateBoolean(m_session, &getInfo, &boolValue), "bool hand input");
568 if (boolValue.isActive && boolValue.changedSinceLastSync) {
569 //qDebug() << "ACTION" << i << def.shortName << bool(boolValue.currentState);
570 m_handInputState[i]->setInputValue(def.id, def.shortName, float(boolValue.currentState));
571 }
572 break;
573 }
574 case XR_ACTION_TYPE_FLOAT_INPUT: {
575 XrActionStateFloat floatValue{};
576 floatValue.type = XR_TYPE_ACTION_STATE_FLOAT;
577 checkXrResult(xrGetActionStateFloat(m_session, &getInfo, &floatValue), "float hand input");
578 if (floatValue.isActive && floatValue.changedSinceLastSync) {
579 //qDebug() << "ACTION" << i << def.shortName << floatValue.currentState;
580 m_handInputState[i]->setInputValue(def.id, def.shortName, float(floatValue.currentState));
581 }
582 break;
583 }
584 case XR_ACTION_TYPE_VECTOR2F_INPUT:
585 case XR_ACTION_TYPE_POSE_INPUT:
586 case XR_ACTION_TYPE_VIBRATION_OUTPUT:
587 case XR_ACTION_TYPE_MAX_ENUM:
588 break;
589 }
590 }
591
592 // Get pose activity status
593 getInfo.action = m_handActions.gripPoseAction;
594 XrActionStatePose poseState{};
595 poseState.type = XR_TYPE_ACTION_STATE_POSE;
596 checkXrResult(xrGetActionStatePose(m_session, &getInfo, &poseState), "xrGetActionStatePose XR_TYPE_ACTION_STATE_POSE");
597 inputState->setIsActive(poseState.isActive);
598
599 // TODO handle any output as well here (haptics)
600 // XrAction gripPoseAction{XR_NULL_HANDLE};
601 // XrAction aimPoseAction{XR_NULL_HANDLE};
602 // XrAction hapticAction{XR_NULL_HANDLE};
603
604 }
605
606 // Gamepad
607 if (!m_disableGamepad) {
608 getInfo.subactionPath = m_gamepadSubactionPath;
609
610 // TODO: refactor duplicated logic
611 for (const auto &def : m_gamepadInputActionDefs) {
612 getInfo.action = m_inputActions[def.id];
613 switch (def.type) {
614 case XR_ACTION_TYPE_BOOLEAN_INPUT: {
615 XrActionStateBoolean boolValue{};
616 boolValue.type = XR_TYPE_ACTION_STATE_BOOLEAN;
617 checkXrResult(xrGetActionStateBoolean(m_session, &getInfo, &boolValue), "bool hand input");
618 if (boolValue.isActive && boolValue.changedSinceLastSync) {
619 //qDebug() << "ACTION" << i << def.shortName << bool(boolValue.currentState);
620 m_gamepadInputState->setInputValue(def.id, def.shortName, float(boolValue.currentState));
621 }
622 break;
623 }
624 case XR_ACTION_TYPE_FLOAT_INPUT: {
625 XrActionStateFloat floatValue{};
626 floatValue.type = XR_TYPE_ACTION_STATE_FLOAT;
627 checkXrResult(xrGetActionStateFloat(m_session, &getInfo, &floatValue), "float hand input");
628 if (floatValue.isActive && floatValue.changedSinceLastSync) {
629 //qDebug() << "ACTION" << i << def.shortName << floatValue.currentState;
630 m_gamepadInputState->setInputValue(def.id, def.shortName, float(floatValue.currentState));
631 }
632 break;
633 }
634 case XR_ACTION_TYPE_VECTOR2F_INPUT:
635 case XR_ACTION_TYPE_POSE_INPUT:
636 case XR_ACTION_TYPE_VIBRATION_OUTPUT:
637 case XR_ACTION_TYPE_MAX_ENUM:
638 break;
639 }
640 }
641
642 // TODO handle any outputs as well here (haptics)
643// XrAction hapticLeftAction{XR_NULL_HANDLE};
644// XrAction hapticRightAction{XR_NULL_HANDLE};
645// XrAction hapticLeftTriggerAction{XR_NULL_HANDLE};
646// XrAction hapticRightTriggerAction{XR_NULL_HANDLE};
647 }
648}
649
650void QOpenXRInputManager::updatePoses(XrTime predictedDisplayTime, XrSpace appSpace)
651{
652 // Update the Hands pose
653
655 XrSpaceLocation spaceLocation{};
656 spaceLocation.type = XR_TYPE_SPACE_LOCATION;
657 XrResult res;
658 res = xrLocateSpace(handSpace(hand), appSpace, predictedDisplayTime, &spaceLocation);
659 // qDebug() << "LOCATE SPACE hand:" << hand << "res" << res << "flags" << spaceLocation.locationFlags
660 // << "active" << m_handInputState[hand]->isActive()
661 // << "Pos" << spaceLocation.pose.position.x << spaceLocation.pose.position.y << spaceLocation.pose.position.z;
662 checkXrResult(res, "xrLocateSpace");
663 m_validAimStateFromUpdatePoses[hand] = m_handInputState[hand]->poseSpace() == QOpenXRHandInput::HandPoseSpace::AimPose
664 && XR_UNQUALIFIED_SUCCESS(res) && (spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT)
665 && (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT); // ### Workaround for Quest issue with hand interaction aim pose
666
667 if (XR_UNQUALIFIED_SUCCESS(res)) {
668 if ((spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 &&
669 (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0) {
670
671 // Update hand transform
672 setPosePosition(hand, QVector3D(spaceLocation.pose.position.x,
673 spaceLocation.pose.position.y,
674 spaceLocation.pose.position.z) * 100.0f);
675 setPoseRotation(hand, QQuaternion(spaceLocation.pose.orientation.w,
676 spaceLocation.pose.orientation.x,
677 spaceLocation.pose.orientation.y,
678 spaceLocation.pose.orientation.z));
679 }
680 } else {
681 // Tracking loss is expected when the hand is not active so only log a message
682 // if the hand is active.
683 if (isHandActive(hand)) {
684 const char* handName[] = {"left", "right"};
685 qDebug("Unable to locate %s hand action space in app space: %d", handName[hand], res);
686 }
687 }
688 }
689}
690
691void QOpenXRInputManager::updateHandtracking(XrTime predictedDisplayTime, XrSpace appSpace, bool aimExtensionEnabled)
692{
694
695 XrHandTrackingAimStateFB aimState[2] = {{}, {}}; // Only used when aim extension is enabled
696 XrHandJointVelocitiesEXT velocities[2]{{}, {}};
697 XrHandJointLocationsEXT locations[2]{{}, {}};
698 XrHandJointsLocateInfoEXT locateInfo[2] = {{}, {}};
699
701 if (handTracker[hand] == XR_NULL_HANDLE)
702 continue;
703
704 aimState[hand].type = XR_TYPE_HAND_TRACKING_AIM_STATE_FB;
705
706 velocities[hand].type = XR_TYPE_HAND_JOINT_VELOCITIES_EXT;
707 velocities[hand].jointCount = XR_HAND_JOINT_COUNT_EXT;
708 velocities[hand].jointVelocities = jointVelocities[hand];
709 velocities[hand].next = aimExtensionEnabled ? &aimState[hand] : nullptr;
710
711 locations[hand].type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT;
712 locations[hand].next = &velocities[hand];
713 locations[hand].jointCount = XR_HAND_JOINT_COUNT_EXT;
714 locations[hand].jointLocations = jointLocations[hand];
715
716 locateInfo[hand].type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT;
717 locateInfo[hand].baseSpace = appSpace;
718 locateInfo[hand].time = predictedDisplayTime;
719 checkXrResult(xrLocateHandJointsEXT_(handTracker[hand], &locateInfo[hand], &locations[hand]), "handTracker");
720
721 QList<QVector3D> jp;
722 jp.reserve(XR_HAND_JOINT_COUNT_EXT);
723 QList<QQuaternion> jr;
724 jr.reserve(XR_HAND_JOINT_COUNT_EXT);
725 for (uint i = 0; i < locations[hand].jointCount; ++i) {
726 auto &pose = jointLocations[hand][i].pose;
727 jp.append(OpenXRHelpers::toQVector(pose.position));
728 jr.append(OpenXRHelpers::toQQuaternion(pose.orientation));
729 }
730 m_handTrackerInputState[hand]->setJointPositionsAndRotations(jp, jr);
731 m_handTrackerInputState[hand]->setIsActive(locations[hand].isActive);
732 }
733
734 if (aimExtensionEnabled) {
735 // Finger pinch handling
737 const uint state = aimState[hand].status;
738 const uint oldState = m_aimStateFlags[hand];
739 auto updateState = [&](const char *name, QOpenXRActionMapper::InputAction id, uint flag) {
740 if ((state & flag) != (oldState & flag))
741 m_handInputState[hand]->setInputValue(id, name, float(!!(state & flag)));
742 };
743
744 updateState("index_pinch", QOpenXRActionMapper::IndexFingerPinch, XR_HAND_TRACKING_AIM_INDEX_PINCHING_BIT_FB);
745 updateState("middle_pinch", QOpenXRActionMapper::MiddleFingerPinch, XR_HAND_TRACKING_AIM_MIDDLE_PINCHING_BIT_FB);
746 updateState("ring_pinch", QOpenXRActionMapper::RingFingerPinch, XR_HAND_TRACKING_AIM_RING_PINCHING_BIT_FB);
747 updateState("little_pinch", QOpenXRActionMapper::LittleFingerPinch, XR_HAND_TRACKING_AIM_LITTLE_PINCHING_BIT_FB);
748 updateState("hand_tracking_menu_press", QOpenXRActionMapper::HandTrackingMenuPress, XR_HAND_TRACKING_AIM_MENU_PRESSED_BIT_FB);
749 m_aimStateFlags[hand] = state;
750 }
751
752 // ### Workaround for Quest issue with hand interaction aim pose
754 if (!m_validAimStateFromUpdatePoses[hand]) {
755 if ((aimState[hand].status & XR_HAND_TRACKING_AIM_VALID_BIT_FB) && m_handInputState[hand]->poseSpace() == QOpenXRHandInput::HandPoseSpace::AimPose) {
756 setPosePosition(hand, QVector3D(aimState[hand].aimPose.position.x,
757 aimState[hand].aimPose.position.y,
758 aimState[hand].aimPose.position.z) * 100.0f);
759 setPoseRotation(hand, QQuaternion(aimState[hand].aimPose.orientation.w,
760 aimState[hand].aimPose.orientation.x,
761 aimState[hand].aimPose.orientation.y,
762 aimState[hand].aimPose.orientation.z));
763 m_handInputState[hand]->setIsActive(true); // TODO: clean up
764 }
765 }
766 }
767 }
768 }
769}
770
771void QOpenXRInputManager::setupHandTracking()
772{
773 checkXrResult(xrGetInstanceProcAddr(
774 m_instance,
775 "xrCreateHandTrackerEXT",
776 (PFN_xrVoidFunction*)(&xrCreateHandTrackerEXT_)), "xrCreateHandTrackerEXT");
777 checkXrResult(xrGetInstanceProcAddr(
778 m_instance,
779 "xrDestroyHandTrackerEXT",
780 (PFN_xrVoidFunction*)(&xrDestroyHandTrackerEXT_)), "xrDestroyHandTrackerEXT");
781 checkXrResult(xrGetInstanceProcAddr(
782 m_instance,
783 "xrLocateHandJointsEXT",
784 (PFN_xrVoidFunction*)(&xrLocateHandJointsEXT_)), "xrLocateHandJointsEXT");
785 checkXrResult(xrGetInstanceProcAddr(
786 m_instance,
787 "xrGetHandMeshFB",
788 (PFN_xrVoidFunction*)(&xrGetHandMeshFB_)), "xrGetHandMeshFB");
789
791 XrHandTrackerCreateInfoEXT createInfo{};
792 createInfo.type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT;
793 createInfo.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT;
794 createInfo.hand = XR_HAND_LEFT_EXT;
795 checkXrResult(xrCreateHandTrackerEXT_(m_session, &createInfo, &handTracker[LeftHand]), "xrCreateHandTrackerEXT handTrackerLeft");
796 createInfo.hand = XR_HAND_RIGHT_EXT;
797 checkXrResult(xrCreateHandTrackerEXT_(m_session, &createInfo, &handTracker[RightHand]), "xrCreateHandTrackerEXT handTrackerRight");
798 }
799 if (xrGetHandMeshFB_) {
801 if (queryHandMesh(hand))
802 createHandModelData(hand);
803 }
804 }
805}
806
807bool QOpenXRInputManager::queryHandMesh(Hand hand)
808{
809 XrHandTrackingMeshFB mesh {};
810 mesh.type = XR_TYPE_HAND_TRACKING_MESH_FB;
811 // Left hand
812 if (!checkXrResult(xrGetHandMeshFB_(handTracker[hand], &mesh))) {
813 qWarning("Failed to query hand mesh info.");
814 return false;
815 }
816
817 mesh.jointCapacityInput = mesh.jointCountOutput;
818 mesh.vertexCapacityInput = mesh.vertexCountOutput;
819 mesh.indexCapacityInput = mesh.indexCountOutput;
820 m_handMeshData[hand].vertexPositions.resize(mesh.vertexCapacityInput);
821 m_handMeshData[hand].vertexNormals.resize(mesh.vertexCapacityInput);
822 m_handMeshData[hand].vertexUVs.resize(mesh.vertexCapacityInput);
823 m_handMeshData[hand].vertexBlendIndices.resize(mesh.vertexCapacityInput);
824 m_handMeshData[hand].vertexBlendWeights.resize(mesh.vertexCapacityInput);
825 m_handMeshData[hand].indices.resize(mesh.indexCapacityInput);
826 mesh.jointBindPoses = m_handMeshData[hand].jointBindPoses;
827 mesh.jointParents = m_handMeshData[hand].jointParents;
828 mesh.jointRadii = m_handMeshData[hand].jointRadii;
829 mesh.vertexPositions = m_handMeshData[hand].vertexPositions.data();
830 mesh.vertexNormals = m_handMeshData[hand].vertexNormals.data();
831 mesh.vertexUVs = m_handMeshData[hand].vertexUVs.data();
832 mesh.vertexBlendIndices = m_handMeshData[hand].vertexBlendIndices.data();
833 mesh.vertexBlendWeights = m_handMeshData[hand].vertexBlendWeights.data();
834 mesh.indices = m_handMeshData[hand].indices.data();
835
836 if (!checkXrResult(xrGetHandMeshFB_(handTracker[hand], &mesh))) {
837 qWarning("Failed to get hand mesh data.");
838 return false;
839 }
840
841 return true;
842};
843
844void QOpenXRInputManager::setupActions()
845{
846 m_handInputActionDefs = {
847 { QOpenXRActionMapper::Button1Pressed, "b1_pressed", "Button 1 Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
848 { QOpenXRActionMapper::Button1Touched, "b1_touched", "Button 1 Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
849 { QOpenXRActionMapper::Button2Pressed, "b2_pressed", "Button 2 Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
850 { QOpenXRActionMapper::Button2Touched, "b2_touched", "Button 2 Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
851 { QOpenXRActionMapper::ButtonMenuPressed, "bmenu_pressed", "Button Menu Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
852 { QOpenXRActionMapper::ButtonMenuTouched, "bmenu_touched", "Button Menu Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
853 { QOpenXRActionMapper::ButtonSystemPressed, "bsystem_pressed", "Button System Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
854 { QOpenXRActionMapper::ButtonSystemTouched, "bsystem_touched", "Button System Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
855 { QOpenXRActionMapper::SqueezeValue, "squeeze_value", "Squeeze Value", XR_ACTION_TYPE_FLOAT_INPUT },
856 { QOpenXRActionMapper::SqueezeForce, "squeeze_force", "Squeeze Force", XR_ACTION_TYPE_FLOAT_INPUT },
857 { QOpenXRActionMapper::SqueezePressed, "squeeze_pressed", "Squeeze Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
858 { QOpenXRActionMapper::TriggerValue, "trigger_value", "Trigger Value", XR_ACTION_TYPE_FLOAT_INPUT },
859 { QOpenXRActionMapper::TriggerPressed, "trigger_pressed", "Trigger Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
860 { QOpenXRActionMapper::TriggerTouched, "trigger_touched", "Trigger Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
861 { QOpenXRActionMapper::ThumbstickX, "thumbstick_x", "Thumbstick X", XR_ACTION_TYPE_FLOAT_INPUT },
862 { QOpenXRActionMapper::ThumbstickY, "thumbstick_y", "Thumbstick Y", XR_ACTION_TYPE_FLOAT_INPUT },
863 { QOpenXRActionMapper::ThumbstickPressed, "thumbstick_pressed", "Thumbstick Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
864 { QOpenXRActionMapper::ThumbstickTouched, "thumbstick_touched", "Thumbstick Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
865 { QOpenXRActionMapper::ThumbrestTouched, "thumbrest_touched", "Thumbrest Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
866 { QOpenXRActionMapper::TrackpadX, "trackpad_x", "Trackpad X", XR_ACTION_TYPE_FLOAT_INPUT },
867 { QOpenXRActionMapper::TrackpadY, "trackpad_y", "Trackpad Y", XR_ACTION_TYPE_FLOAT_INPUT },
868 { QOpenXRActionMapper::TrackpadForce, "trackpad_force", "Trackpad Force", XR_ACTION_TYPE_FLOAT_INPUT },
869 { QOpenXRActionMapper::TrackpadTouched, "trackpad_touched", "Trackpad Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
870 { QOpenXRActionMapper::TrackpadPressed, "trackpad_pressed", "Trackpad Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT }
871 };
872
873 // Create an action set.
874 {
875 XrActionSetCreateInfo actionSetInfo{};
876 actionSetInfo.type = XR_TYPE_ACTION_SET_CREATE_INFO;
877 strcpy(actionSetInfo.actionSetName, "gameplay");
878 strcpy(actionSetInfo.localizedActionSetName, "Gameplay");
879 actionSetInfo.priority = 0;
880 checkXrResult(xrCreateActionSet(m_instance, &actionSetInfo, &m_actionSet), "xrCreateActionSet gameplay");
881 }
882
883 // Create Hand Actions
884 setPath(m_handSubactionPath[0], "/user/hand/left");
885 setPath(m_handSubactionPath[1], "/user/hand/right");
886
887 for (const auto &def : m_handInputActionDefs) {
888 createAction(def.type,
889 def.shortName,
890 def.localizedName,
891 2,
892 m_handSubactionPath,
893 m_inputActions[def.id]);
894 }
895
896 createAction(XR_ACTION_TYPE_VIBRATION_OUTPUT,
897 "vibrate_hand",
898 "Vibrate Hand",
899 2,
900 m_handSubactionPath,
901 m_handActions.hapticAction);
902 createAction(XR_ACTION_TYPE_POSE_INPUT,
903 "hand_grip_pose",
904 "Hand Grip Pose",
905 2,
906 m_handSubactionPath,
907 m_handActions.gripPoseAction);
908 createAction(XR_ACTION_TYPE_POSE_INPUT,
909 "hand_aim_pose",
910 "Hand Aim Pose",
911 2,
912 m_handSubactionPath,
913 m_handActions.aimPoseAction);
914
915 // Create Gamepad Actions
916 if (!m_disableGamepad) {
917
918 m_gamepadInputActionDefs = {
919 { QOpenXRActionMapper::GamepadButtonMenuPressed, "gp_bmenu_pressed", "Gamepad Button Menu Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
920 { QOpenXRActionMapper::GamepadButtonViewPressed, "gp_bview_pressed", "Gamepad Button View Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
921 { QOpenXRActionMapper::GamepadButtonAPressed, "gp_ba_pressed", "Gamepad Button A Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
922 { QOpenXRActionMapper::GamepadButtonBPressed, "gp_bb_pressed", "Gamepad Button B Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
923 { QOpenXRActionMapper::GamepadButtonXPressed, "gp_bx_pressed", "Gamepad Button Y Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
924 { QOpenXRActionMapper::GamepadButtonYPressed, "gp_by_pressed", "Gamepad Button X Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
925 { QOpenXRActionMapper::GamepadButtonDownPressed, "gp_bdown_pressed", "Gamepad Button Down Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
926 { QOpenXRActionMapper::GamepadButtonRightPressed, "gp_bright_pressed", "Gamepad Button Right Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
927 { QOpenXRActionMapper::GamepadButtonUpPressed, "gp_bup_pressed", "Gamepad Button Up Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
928 { QOpenXRActionMapper::GamepadButtonLeftPressed, "gp_bleft_pressed", "Gamepad Button Left Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
929 { QOpenXRActionMapper::GamepadShoulderLeftPressed, "gp_sleft_pressed", "Gamepad Shoulder Left Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
930 { QOpenXRActionMapper::GamepadShoulderRightPressed, "gp_sright_pressed", "Gamepad Shoulder Right Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
931 { QOpenXRActionMapper::GamepadThumbstickLeftPressed, "gp_tsleft_pressed", "Gamepad Thumbstick Left Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
932 { QOpenXRActionMapper::GamepadThumbstickRightPressed, "gp_tsright_pressed", "Gamepad Thumbstick Right Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
933 { QOpenXRActionMapper::GamepadTriggerLeft, "gp_tleft_value", "Gamepad Trigger Left", XR_ACTION_TYPE_BOOLEAN_INPUT },
934 { QOpenXRActionMapper::GamepadTriggerRight, "gp_tright_value", "Gamepad Trigger Right", XR_ACTION_TYPE_BOOLEAN_INPUT },
935 { QOpenXRActionMapper::GamepadThumbstickLeftX, "gp_tsleft_x_value", "Gamepad Thumbstick Left X", XR_ACTION_TYPE_BOOLEAN_INPUT },
936 { QOpenXRActionMapper::GamepadThumbstickLeftY, "gp_tsleft_y_value", "Gamepad Thumbstick Left Y", XR_ACTION_TYPE_BOOLEAN_INPUT },
937 { QOpenXRActionMapper::GamepadThumbstickRightX, "gp_tsright_x_value", "Gamepad Thumbstick Right X", XR_ACTION_TYPE_BOOLEAN_INPUT },
938 { QOpenXRActionMapper::GamepadThumbstickRightY, "gp_tsright_y_value", "Gamepad Thumbstick Right Y", XR_ACTION_TYPE_BOOLEAN_INPUT }
939 };
940
941 setPath(m_gamepadSubactionPath, "/user/gamepad");
942
943 for (const auto &def : m_gamepadInputActionDefs) {
944 createAction(def.type,
945 def.shortName,
946 def.localizedName,
947 1,
948 &m_gamepadSubactionPath,
949 m_inputActions[def.id]);
950 }
951
952 createAction(XR_ACTION_TYPE_VIBRATION_OUTPUT,
953 "gp_vibrate_left",
954 "Gamepad Vibrate Left",
955 1,
956 &m_gamepadSubactionPath,
957 m_gamepadActions.hapticLeftAction);
958 createAction(XR_ACTION_TYPE_VIBRATION_OUTPUT,
959 "gp_vibrate_right",
960 "Gamepad Vibrate Right",
961 1,
962 &m_gamepadSubactionPath,
963 m_gamepadActions.hapticRightAction);
964 createAction(XR_ACTION_TYPE_VIBRATION_OUTPUT,
965 "gp_vibrate_trigger_left",
966 "Gamepad Vibrate Trigger Left",
967 1,
968 &m_gamepadSubactionPath,
969 m_gamepadActions.hapticLeftTriggerAction);
970 createAction(XR_ACTION_TYPE_VIBRATION_OUTPUT,
971 "gp_vibrate_trigger_right",
972 "Gamepad Vibrate Trigger Right",
973 1,
974 &m_gamepadSubactionPath,
975 m_gamepadActions.hapticRightTriggerAction);
976 }
977}
978
979void QOpenXRInputManager::destroyActions()
980{
981 for (auto &action : m_inputActions) {
982 if (action)
983 xrDestroyAction(action);
984 }
985
986 xrDestroyAction(m_handActions.gripPoseAction);
987 xrDestroyAction(m_handActions.aimPoseAction);
988 xrDestroyAction(m_handActions.hapticAction);
989
990 if (!m_disableGamepad) {
991 xrDestroyAction(m_gamepadActions.hapticLeftAction);
992 xrDestroyAction(m_gamepadActions.hapticRightAction);
993 xrDestroyAction(m_gamepadActions.hapticLeftTriggerAction);
994 xrDestroyAction(m_gamepadActions.hapticRightTriggerAction);
995 }
996
997 xrDestroyActionSet(m_actionSet);
998}
999
1000bool QOpenXRInputManager::checkXrResult(const XrResult &result, const char *debugText)
1001{
1002 bool checkResult = OpenXRHelpers::checkXrResult(result, m_instance);
1003 if (!checkResult) {
1004 qDebug() << "checkXrResult failed" << result << (debugText ? debugText : "");
1005 }
1006 return checkResult;
1007
1008}
1009
1010void QOpenXRInputManager::setPath(XrPath &path, const QByteArray &pathString)
1011{
1012 checkXrResult(xrStringToPath(m_instance, pathString.constData(), &path), "xrStringToPath");
1013}
1014
1015void QOpenXRInputManager::createAction(XrActionType type,
1016 const char *name,
1017 const char *localizedName,
1018 int numSubactions,
1019 XrPath *subactionPath,
1020 XrAction &action)
1021{
1022 XrActionCreateInfo actionInfo{};
1023 actionInfo.type = XR_TYPE_ACTION_CREATE_INFO;
1024 actionInfo.actionType = type;
1025 strcpy(actionInfo.actionName, name);
1026 strcpy(actionInfo.localizedActionName, localizedName);
1027 actionInfo.countSubactionPaths = quint32(numSubactions);
1028 actionInfo.subactionPaths = subactionPath;
1029 bool res = checkXrResult(xrCreateAction(m_actionSet, &actionInfo, &action), "xrCreateAction");
1030 if (!res)
1031 qDebug() << "xrCreateAction failed. Name:" << name << "localizedName:" << localizedName;
1032}
1033
1034void QOpenXRInputManager::getBoolInputState(XrActionStateGetInfo &getInfo, const XrAction &action, std::function<void(bool)> setter)
1035{
1036 getInfo.action = action;
1037 XrActionStateBoolean boolValue{};
1038 boolValue.type = XR_TYPE_ACTION_STATE_BOOLEAN;
1039 checkXrResult(xrGetActionStateBoolean(m_session, &getInfo, &boolValue), "getBoolInputState");
1040 if (boolValue.isActive == XR_TRUE)
1041 setter(bool(boolValue.currentState));
1042}
1043
1044void QOpenXRInputManager::getFloatInputState(XrActionStateGetInfo &getInfo, const XrAction &action, std::function<void(float)> setter)
1045{
1046 getInfo.action = action;
1047 XrActionStateFloat floatValue{};
1048 floatValue.type = XR_TYPE_ACTION_STATE_FLOAT;
1049 checkXrResult(xrGetActionStateFloat(m_session, &getInfo, &floatValue), "getFloatInputState");
1050 if (floatValue.isActive == XR_TRUE)
1051 setter(float(floatValue.currentState));
1052}
1053
1055{
1056 if (m_handInputState[hand]->poseSpace() == QOpenXRHandInput::HandPoseSpace::GripPose)
1057 return m_handGripSpace[hand];
1058 else
1059 return m_handAimSpace[hand];
1060}
1061
1063{
1064 if (m_handTrackerInputState[handtracker]->poseSpace() == QOpenXRHandTrackerInput::HandPoseSpace::GripPose)
1065 return m_handGripSpace[handtracker];
1066 else
1067 return m_handAimSpace[handtracker];
1068}
1069
1071{
1072 return m_handInputState[hand]->isActive();
1073}
1074
1076{
1077 return m_handTrackerInputState[handtracker]->isActive();
1078}
1079
1081{
1082 m_handInputState[hand]->setPosePosition(position);
1083}
1084
1086{
1087 m_handInputState[hand]->setPoseRotation(rotation);
1088}
1089
1094
1099
1104
1109
1111{
1112 return m_gamepadInputState;
1113}
1114
1115void QOpenXRInputManager::createHandModelData(Hand hand)
1116{
1117 const auto &handMeshData = m_handMeshData[hand];
1118
1119 auto &geometry = m_handGeometryData[hand].geometry;
1120 delete geometry;
1121 geometry = createHandMeshGeometry(handMeshData);
1122}
1123
bool isActive
\inmodule QtCore
Definition qbytearray.h:57
void reserve(qsizetype size)
Attempts to allocate memory for at least size bytes.
Definition qbytearray.h:634
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setInputValue(int id, const char *shortName, float value)
HandPoseSpace poseSpace
void setPosePosition(const QVector3D &position)
void setPoseRotation(const QQuaternion &rotation)
void setIsActive(bool isActive)
void setInputValue(int id, const char *shortName, float value)
void setJointPositionsAndRotations(const QList< QVector3D > &newJointPositions, const QList< QQuaternion > &newJointRotations)
XrHandTrackerEXT handTracker[2]
bool isHandTrackerActive(Hand handtracker)
XrHandJointLocationEXT jointLocations[2][XR_HAND_JOINT_COUNT_EXT]
XrHandJointVelocityEXT jointVelocities[2][XR_HAND_JOINT_COUNT_EXT]
PFN_xrCreateHandTrackerEXT xrCreateHandTrackerEXT_
PFN_xrGetHandMeshFB xrGetHandMeshFB_
PFN_xrDestroyHandTrackerEXT xrDestroyHandTrackerEXT_
QOpenXRHandTrackerInput * leftHandTrackerInput() const
void setPosePosition(Hand hand, const QVector3D &position)
void updatePoses(XrTime predictedDisplayTime, XrSpace appSpace)
static QOpenXRInputManager * instance()
QOpenXRGamepadInput * gamepadInput() const
XrSpace handTrackerSpace(Hand handtracker)
QOpenXRHandInput * leftHandInput() const
void setPoseRotation(Hand hand, const QQuaternion &rotation)
XrSpace handSpace(Hand hand)
QOpenXRHandInput * rightHandInput() const
PFN_xrLocateHandJointsEXT xrLocateHandJointsEXT_
void init(XrInstance instance, XrSession session)
void updateHandtracking(XrTime predictedDisplayTime, XrSpace appSpace, bool aimExtensionEnabled)
QOpenXRHandTrackerInput * rightHandTrackerInput() const
The QQuaternion class represents a quaternion consisting of a vector and scalar.
\qmltype Geometry \inherits Object3D \inqmlmodule QtQuick3D \instantiates QQuick3DGeometry
void setPrimitiveType(PrimitiveType type)
Sets the primitive type used for rendering to type.
void setStride(int stride)
Sets the stride of the vertex buffer to stride, measured in bytes.
void addAttribute(Attribute::Semantic semantic, int offset, Attribute::ComponentType componentType)
Adds vertex attribute description.
void setVertexData(const QByteArray &data)
Sets the vertex buffer data.
void setBounds(const QVector3D &min, const QVector3D &max)
Sets the bounding volume of the geometry to the cube defined by the points min and max.
void setIndexData(const QByteArray &data)
Sets the index buffer to data.
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
else opt state
[0]
QQuaternion toQQuaternion(const XrQuaternionf &q)
bool checkXrResult(XrResult result, XrInstance instance)
QVector3D toQVector(const XrVector3f &v)
Combined button and popup list for selecting options.
void setter(QUntypedPropertyData *d, const void *value)
QString boolValue(bool v)
Definition language.cpp:493
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLuint index
[2]
GLenum GLuint id
[7]
GLfloat GLfloat f
GLuint GLuint GLfloat weight
GLenum GLsizei const void * pathString
const void GLsizei GLsizei stride
GLenum type
GLenum GLuint GLintptr offset
GLuint name
GLuint res
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
GLuint const GLint * locations
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
unsigned int quint32
Definition qtypes.h:50
int qint32
Definition qtypes.h:49
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned int uint
Definition qtypes.h:34
QFileSelector selector
[1]