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