Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquick3dxrinputmanager_visionos.mm
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
6
7#include "../qquick3dxrinputmanager_p.h"
8#include "../qquick3dxractionmapper_p.h"
9#include "../qquick3dxrcontroller_p.h"
10
11#include <QtQuick3DUtils/private/qssgassert_p.h>
12#include <QtQuick3DUtils/private/qssgutils_p.h>
13
14#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
15#include <QtQuick3D/private/qquick3dviewport_p.h>
16
18
19Q_DECLARE_LOGGING_CATEGORY(lcQuick3DXr);
20
22 : q_ptr(&manager)
23{
24 m_handInputState[Hand::LeftHand] = new QQuick3DXrHandInput(&manager);
25 m_handInputState[Hand::RightHand] = new QQuick3DXrHandInput(&manager);
26}
27
29{
30
31}
32
33static inline void setInputValue(QtQuick3DXr::Hand hand, int id, const char *shortName, float value)
34{
35 QSSG_ASSERT(hand < 2, hand = QtQuick3DXr::Hand::LeftHand);
36 QQuick3DXrActionMapper::handleInput(QQuick3DXrInputAction::Action(id), static_cast<QQuick3DXrInputAction::Controller>(hand), shortName, value);
37}
38
39
40void QQuick3DXrInputManagerPrivate::prepareHandtracking(ar_data_providers_t dataProviders)
41{
42 QSSG_ASSERT_X(!m_initialized, "Handtracking is already initialized!", return);
43
44 m_isHandTrackingSupported = ar_hand_tracking_provider_is_supported();
45 if (m_isHandTrackingSupported) {
46 ar_hand_tracking_configuration_t handTrackingConfiguration = ar_hand_tracking_configuration_create();
47 m_handTrackingProvider = ar_hand_tracking_provider_create(handTrackingConfiguration);
48 ar_data_providers_add_data_provider(dataProviders, m_handTrackingProvider);
49 } else {
50 qCWarning(lcQuick3DXr, "Handtracking is not supported on this device!");
51 }
52
53 qCDebug(lcQuick3DXr) << Q_FUNC_INFO << ", Handtracking supported: " << m_isHandTrackingSupported;
54}
55
57{
58 if (m_isHandTrackingSupported) {
59 m_handAnchors[Hand::LeftHand] = ar_hand_anchor_create();
60 m_handAnchors[Hand::RightHand] = ar_hand_anchor_create();
61 m_initialized = true;
62 }
63
64 qCDebug(lcQuick3DXr) << Q_FUNC_INFO << ", Initialized: " << m_initialized;
65}
66
68{
69}
70
71void QQuick3DXrInputManagerPrivate::setPosePositionAndRotation(Hand hand, HandPoseSpace poseSpace, const QVector3D &position, const QQuaternion &rotation)
72{
73 for (QQuick3DXrController *controller : std::as_const(m_controllers)) {
74 if (QtQuick3DXr::handForController(controller->controller()) == hand && QtQuick3DXr::pose_cast(controller->poseSpace()) == poseSpace) {
75 controller->setPosition(position);
76 controller->setRotation(rotation);
77 }
78 }
79}
80
81// Used both to add a new controller, and notify that an existing one has changed
82void QQuick3DXrInputManagerPrivate::registerController(QQuick3DXrController *controller)
83{
84 m_poseUsageDirty = true;
85 if (controller->controller() == QQuick3DXrController::ControllerNone) {
86 m_controllers.remove(controller);
87 return;
88 }
89 // No point in checking whether it's already in the set: that's just as expensive as inserting
90 m_controllers.insert(controller);
91}
92
93void QQuick3DXrInputManagerPrivate::unregisterController(QQuick3DXrController *controller)
94{
95 m_poseUsageDirty = m_controllers.remove(controller);
96}
97
98bool QQuick3DXrInputManagerPrivate::isPoseInUse(Hand hand, HandPoseSpace poseSpace)
99{
100 QSSG_ASSERT(uint(hand) < 2 && uint(poseSpace) < 2, return false);
101 if (m_poseUsageDirty) {
102 std::fill_n(&m_poseInUse[0][0], 4, false);
103 for (const auto *controller : std::as_const(m_controllers)) {
104 m_poseInUse[uint(controller->controller())][uint(controller->poseSpace())] = true;
105 }
106 m_poseUsageDirty = false;
107 }
108 return m_poseInUse[uint(hand)][uint(poseSpace)];
109}
110
112{
113 return m_handInputState[Hand::LeftHand];
114}
115
117{
118 return m_handInputState[Hand::RightHand];
119}
120
121QQuick3DXrInputManagerPrivate *QQuick3DXrInputManagerPrivate::get(QQuick3DXrInputManager *inputManager)
122{
123 return inputManager->d_func();
124}
125
127{
128 Q_UNUSED(model);
129}
130
131static bool setupJoint(ar_hand_skeleton_joint_name_t jointName, const ar_hand_skeleton_t handSkeleton, const simd_float4x4 handTransform, QVector3D &jointPosition, QQuaternion &jointRotation)
132{
133 bool isTracked = false;
134 ar_skeleton_joint_t joint = ar_hand_skeleton_get_joint_named(handSkeleton, jointName);
135 if (joint != nullptr) {
136 if (ar_skeleton_joint_is_tracked(joint)) {
137 simd_float4x4 jointTransform = ar_skeleton_joint_get_anchor_from_joint_transform(joint);
138 jointTransform = simd_mul(handTransform, jointTransform);
139
140 QMatrix4x4 transform{jointTransform.columns[0].x, jointTransform.columns[1].x, jointTransform.columns[2].x, jointTransform.columns[3].x,
141 jointTransform.columns[0].y, jointTransform.columns[1].y, jointTransform.columns[2].y, jointTransform.columns[3].y,
142 jointTransform.columns[0].z, jointTransform.columns[1].z, jointTransform.columns[2].z, jointTransform.columns[3].z,
143 0.0f, 0.0f, 0.0f, 1.0f};
144
145
146 QVector3D jp;
147 QVector3D scale;
148 QQuaternion jr;
149 QSSGUtils::mat44::decompose(transform, jp, scale, jr);
150
151 // NOTE: We need to scale the joint position by 100 to get it into the right scale (m -> cm).
152 jointPosition = jp * 100.0f;
153 jointRotation = jr;
154
155 isTracked = true;
156 }
157 }
158
159 return isTracked;
160}
161
162using HandJointList = QVarLengthArray<ar_hand_skeleton_joint_name_t, 28>;
163
165{
166 // NOTE: "Joints" are placed in the order from the forarm to the fingers (This might differ from Apple's documentation),
167 // moving from the wrist to the finger tips going from the left to the right.
168 // That means the forarm is at position 0, and the wrist is at position 1 (this is the origin).
169
170 static const HandJointList jointNames {
171 ar_hand_skeleton_joint_name_forearm_arm, // This is the forearm.
172
173 // These are technically the same joint, but depends on the orientation of the hand.
174 ar_hand_skeleton_joint_name_wrist, // This is the actual wrist.
175 ar_hand_skeleton_joint_name_forearm_wrist,
176
177 // The thumb.
178 ar_hand_skeleton_joint_name_thumb_knuckle,
179 ar_hand_skeleton_joint_name_thumb_intermediate_base,
180 ar_hand_skeleton_joint_name_thumb_intermediate_tip,
181 ar_hand_skeleton_joint_name_thumb_tip,
182
183 // The index finger.
184 ar_hand_skeleton_joint_name_index_finger_metacarpal,
185 ar_hand_skeleton_joint_name_index_finger_knuckle,
186 ar_hand_skeleton_joint_name_index_finger_intermediate_base,
187 ar_hand_skeleton_joint_name_index_finger_intermediate_tip,
188 ar_hand_skeleton_joint_name_index_finger_tip,
189
190 // The middle finger.
191 ar_hand_skeleton_joint_name_middle_finger_metacarpal,
192 ar_hand_skeleton_joint_name_middle_finger_knuckle,
193 ar_hand_skeleton_joint_name_middle_finger_intermediate_base,
194 ar_hand_skeleton_joint_name_middle_finger_intermediate_tip,
195 ar_hand_skeleton_joint_name_middle_finger_tip,
196
197 // The ring finger.
198 ar_hand_skeleton_joint_name_ring_finger_metacarpal,
199 ar_hand_skeleton_joint_name_ring_finger_knuckle,
200 ar_hand_skeleton_joint_name_ring_finger_intermediate_base,
201 ar_hand_skeleton_joint_name_ring_finger_intermediate_tip,
202 ar_hand_skeleton_joint_name_ring_finger_tip,
203
204 // The little finger.
205 ar_hand_skeleton_joint_name_little_finger_metacarpal,
206 ar_hand_skeleton_joint_name_little_finger_knuckle,
207 ar_hand_skeleton_joint_name_little_finger_intermediate_base,
208 ar_hand_skeleton_joint_name_little_finger_intermediate_tip,
209 ar_hand_skeleton_joint_name_little_finger_tip,
210 };
211
212 return jointNames;
213}
214
215static qsizetype getJointIndex(ar_hand_skeleton_joint_name_t jointName)
216{
217 qsizetype index = -1;
218 const auto &jointNames = getJointNameTable();
219 for (size_t i = 0, e = jointNames.size(); i != e && index == -1; ++i) {
220 if (jointNames[i] == jointName)
221 index = i;
222 }
223
224 return index;
225}
226
227template <QtQuick3DXr::HandPoseSpace HandPose>
228static void getHandPose(ar_hand_skeleton_t handSkeleton, const simd_float4x4 handTransform, QVector3D &posePosition, QQuaternion &poseRotation)
229{
230 QMatrix4x4 qHandTransform{handTransform.columns[0].x, handTransform.columns[1].x, handTransform.columns[2].x, handTransform.columns[3].x,
231 handTransform.columns[0].y, handTransform.columns[1].y, handTransform.columns[2].y, handTransform.columns[3].y,
232 handTransform.columns[0].z, handTransform.columns[1].z, handTransform.columns[2].z, handTransform.columns[3].z,
233 0.0f, 0.0f, 0.0f, 1.0f};
234 simd_float4x4 rotHandTransform = { simd_float4{ qHandTransform(0,0), qHandTransform(1,0), qHandTransform(2,0), qHandTransform(3,0) },
235 simd_float4{ qHandTransform(0,1), qHandTransform(1,1), qHandTransform(2,1), qHandTransform(3,1) },
236 simd_float4{ qHandTransform(0,2), qHandTransform(1,2), qHandTransform(2,2), qHandTransform(3,2) },
237 simd_float4{ qHandTransform(0,3), qHandTransform(1,3), qHandTransform(2,3), qHandTransform(3,3) } };
238
239 if constexpr (HandPose == QtQuick3DXr::HandPoseSpace::AimPose) {
240 // Using the knuckle joint as the aim pose, as it produces a more "stable" feeling when aiming.
241 setupJoint(ar_hand_skeleton_joint_name_index_finger_knuckle, handSkeleton, rotHandTransform, posePosition, poseRotation);
242
243 static QQuaternion rotation = QQuaternion::fromEulerAngles(QVector3D(0, 90, 90));
244 poseRotation = poseRotation * rotation;
245 } else {
246 static_assert(HandPose == QtQuick3DXr::HandPoseSpace::GripPose);
247 setupJoint(ar_hand_skeleton_joint_name_middle_finger_knuckle, handSkeleton, rotHandTransform, posePosition, poseRotation);
248
249 static QQuaternion rotation = QQuaternion::fromEulerAngles(QVector3D(0, 180, 90));
250 poseRotation = poseRotation * rotation;
251 }
252}
253
260
262 { "", 0.0, QQuick3DXrInputAction::CustomAction },
263 { "index_pinch", 0.005, QQuick3DXrInputAction::IndexFingerPinch },
264 { "middle_pinch", 0.009, QQuick3DXrInputAction::MiddleFingerPinch },
265 { "ring_pinch", 0.009, QQuick3DXrInputAction::RingFingerPinch },
266 { "little_pinch", 0.01, QQuick3DXrInputAction::LittleFingerPinch },
267};
268
269static void detectGestures(ar_hand_skeleton_t handSkeleton, QtQuick3DXr::Hand hand)
270{
271 enum PinchJoints {
272 ThumbTip,
273 IndexFingerTip,
274 MiddleFingerTip,
275 RingFingerTip,
276 LittleFingerTip,
277 };
278
279 const ar_hand_skeleton_joint_name_t pinchJoints[] { ar_hand_skeleton_joint_name_thumb_tip,
280 ar_hand_skeleton_joint_name_index_finger_tip,
281 ar_hand_skeleton_joint_name_middle_finger_tip,
282 ar_hand_skeleton_joint_name_ring_finger_tip,
283 ar_hand_skeleton_joint_name_little_finger_tip };
284
285 constexpr size_t pinchJointCount = std::size(pinchJoints);
286
287 simd_float4x4 jointTransforms[pinchJointCount];
288 // Assume the thumb tip is tracked.
289 bool isTracked[pinchJointCount] { true, false, false, false };
290
291 for (int i = 0, end = pinchJointCount; isTracked[ThumbTip] && i != end; ++i) {
292 ar_hand_skeleton_joint_name_t jointName = pinchJoints[i];
293 if (ar_skeleton_joint_t joint = ar_hand_skeleton_get_joint_named(handSkeleton, jointName)) {
294 if (ar_skeleton_joint_is_tracked(joint)) {
295 jointTransforms[i] = ar_skeleton_joint_get_anchor_from_joint_transform(joint);
296 isTracked[i] = true;
297 }
298 }
299 }
300
301 // If the thumb isn't tracked, we can't do anything, bail out.
302 if (!isTracked[ThumbTip])
303 return;
304
305 // Calculate the distance between the tips and the thumb tip (first one wins).
306 const simd_float4 thumbTip = jointTransforms[ThumbTip].columns[3];
307
308 // Start from the index finger (1).
309 for (int i = 1, end = pinchJointCount; i != end; ++i) {
310 if (!isTracked[i])
311 continue;
312
313 const simd_float4 diff = jointTransforms[i].columns[3] - thumbTip;
314 const float distance = simd_length(diff);
315 setInputValue(hand, VOPinchGestures[i].type, VOPinchGestures[i].name, float(distance < VOPinchGestures[i].pinchDistanceThreshold));
316 }
317}
318
320{
321 // NOTE: Static for now...
322 static const qsizetype idx = getJointIndex(ar_hand_skeleton_joint_name_index_finger_tip);
323 return idx;
324}
325
327{
328 if (!m_isHandTrackingSupported)
329 return;
330
331 QSSG_ASSERT(m_handTrackingProvider != nullptr, return);
332 QSSG_ASSERT(m_handAnchors[Hand::LeftHand] != nullptr && m_handAnchors[Hand::RightHand] != nullptr, return);
333
334 ar_hand_tracking_provider_get_latest_anchors(m_handTrackingProvider, m_handAnchors[Hand::LeftHand], m_handAnchors[Hand::RightHand]);
335
336 // FIXME: We can and probably should cache the hand skeleton.
337 ar_hand_skeleton_t handSkeletons[2] {};
338 uint64_t handJointCount = 0;
339 for (const auto hand : { Hand::LeftHand, Hand::RightHand }) {
340 handSkeletons[hand] = ar_hand_anchor_get_hand_skeleton(m_handAnchors[hand]);
341 handJointCount = qMax(handJointCount, ar_hand_skeleton_get_joint_count(handSkeletons[hand]));
342 }
343
344 const auto &jointNames = getJointNameTable();
345 // Sanity check the joint count.
346 QSSG_CHECK(handJointCount <= size_t(jointNames.size()));
347
348 for (const auto hand : { Hand::LeftHand, Hand::RightHand }) {
349 const auto handSkeleton = handSkeletons[hand];
350 if (handSkeleton == nullptr) {
351 m_handInputState[hand]->setIsHandTrackingActive(false);
352 continue;
353 }
354
355 // Clear cached joint data.
356 auto &jpositions = jcache[hand].positions;
357 auto &jrotations = jcache[hand].rotations;
358 jpositions.clear();
359 jrotations.clear();
360
361 // NOTE: Separate as we're just sanity checking that the wrist is tracked.
362 ar_skeleton_joint_t wristJoinOrigin = ar_hand_skeleton_get_joint_named(handSkeleton, ar_hand_skeleton_joint_name_wrist);
363 const bool isWristTracked = ar_skeleton_joint_is_tracked(wristJoinOrigin);
364
365 // The hand transform is relative to the head anchor. The wrist is at the origin of the hand anchor.
366 const simd_float4x4 handTransform = ar_anchor_get_origin_from_anchor_transform(m_handAnchors[hand]);
367
368 // Get the joint data.
369 for (auto jointName : jointNames) {
370 QVector3D jointPosition;
371 QQuaternion jointRotation;
372 if (setupJoint(jointName, handSkeleton, handTransform, jointPosition, jointRotation)) {
373 jpositions.append(jointPosition);
374 jrotations.append(jointRotation);
375 }
376 }
377
378 // Detect gestures.
379 detectGestures(handSkeleton, hand);
380
381 // Get and set the aim/grip pose.
382
383 if (isPoseInUse(hand, HandPoseSpace::AimPose) ) {
384 QVector3D handPosition;
385 QQuaternion handRotation;
386 getHandPose<HandPoseSpace::AimPose>(handSkeleton, handTransform, handPosition, handRotation);
387
388 setPosePositionAndRotation(hand, HandPoseSpace::AimPose, handPosition, handRotation);
389 } else if (isPoseInUse(hand, HandPoseSpace::GripPose)) {
390 QVector3D handPosition;
391 QQuaternion handRotation;
392 getHandPose<HandPoseSpace::GripPose>(handSkeleton, handTransform, handPosition, handRotation);
393
394 setPosePositionAndRotation(hand, HandPoseSpace::GripPose, handPosition, handRotation);
395 }
396
397 m_handInputState[hand]->setJointPositionsAndRotations(jpositions, jrotations);
398
399 m_handInputState[hand]->setIsActive(isWristTracked);
400 }
401}
402
403void QQuick3DXrInputManagerPrivate::processSpatialEvents(const QQuick3DViewport &vrViewport, const QJsonObject &events)
404{
405 // An example of the input data
406 // {
407 // "id":4404736049417834088,
408 // "inputDevicePose": {
409 // "altitude":0,
410 // "azimuth":1.5707963267948966,
411 // "pose3D":{
412 // "position":{
413 // "x":0.227996826171875,
414 // "y":0.957000732421875,
415 // "z":-0.55999755859375
416 // },
417 // "rotation":{
418 // "vector":[0,0,0,1]
419 // }
420 // }
421 // },
422 // "kind":"indirectPinch",
423 // "location":[0,0],
424 // "location3D":{
425 // "x":0,
426 // "y":0,
427 // "z":0
428 // },
429 // "modifierKeys":0,
430 // "phase":"ended",
431 // "selectionRay":{
432 // "direction":{
433 // "x":0.3321685791015625,
434 // "y":0.25982666015625,
435 // "z":-0.9067230224609375
436 // },
437 // "origin":{
438 // "x":0.227996826171875,
439 // "y":0.957000732421875,
440 // "z":0
441 // }
442 // },
443 // "timestamp":74368.590710375
444 // }
445
446 static qint64 lastId = -1;
447
448 QJsonArray eventArray = events.value(QStringLiteral("events")).toArray();
449 for (const auto &event : eventArray) {
450 QJsonObject eventObj = event.toObject();
451 // qDebug() << eventObj;
452
453 // ID (unique per event)
454 const qint64 id = eventObj.value(QStringLiteral("id")).toDouble();
455 // timestamp (in seconds)
456 //const double timestamp = eventObj.value(QStringLiteral("timestamp")).toDouble();
457 // kind
458 const QString kind = eventObj.value(QStringLiteral("kind")).toString();
459 if (kind != QStringLiteral("indirectPinch"))
460 qWarning() << "kind is " << kind << "!";
461
462
463 // phase
464 const QString phase = eventObj.value(QStringLiteral("phase")).toString();
465
466 // selectionRay (check if exists first)
467 QJsonObject selectionRayObj = eventObj.value(QStringLiteral("selectionRay")).toObject();
468 if (!selectionRayObj.isEmpty()) {
469 // origin
470 QJsonObject originObj = selectionRayObj.value(QStringLiteral("origin")).toObject();
471 QVector3D origin(originObj.value(QStringLiteral("x")).toDouble(), originObj.value(QStringLiteral("y")).toDouble(), originObj.value(QStringLiteral("z")).toDouble());
472 // convert meters to cm
473 origin *= 100.0;
474
475 // direction
476 QJsonObject directionObj = selectionRayObj.value(QStringLiteral("direction")).toObject();
477 QVector3D direction(directionObj.value(QStringLiteral("x")).toDouble(), directionObj.value(QStringLiteral("y")).toDouble(), directionObj.value(QStringLiteral("z")).toDouble());
478
479 QEvent::Type eventType;
480
481 if (phase == QStringLiteral("active")) {
482 if (lastId != id) {
483 // Press
484 lastId = id;
485 eventType = QEvent::MouseButtonPress;
486 } else {
487 // Move
488 eventType = QEvent::MouseMove;
489 }
490 } else {
491 // Release
492 lastId = -1;
493 eventType = QEvent::MouseButtonRelease;
494 }
495
496 QMouseEvent *event = new QMouseEvent(eventType, QPointF(), QPointF(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
497 vrViewport.processPointerEventFromRay(origin, direction, event);
498 delete event;
499 }
500 }
501}
502
503QT_END_NAMESPACE
bool isPoseInUse(Hand hand, HandPoseSpace poseSpace)
void prepareHandtracking(ar_data_providers_t dataProviders)
void setPosePositionAndRotation(Hand hand, HandPoseSpace poseSpace, const QVector3D &position, const QQuaternion &rotation)
QQuick3DXrHandInput * rightHandInput() const
QQuick3DXrInputManagerPrivate(QQuick3DXrInputManager &manager)
void setupHandModel(QQuick3DXrHandModel *model)
void registerController(QQuick3DXrController *controller)
QQuick3DXrHandInput * leftHandInput() const
void unregisterController(QQuick3DXrController *controller)
static void detectGestures(ar_hand_skeleton_t handSkeleton, QtQuick3DXr::Hand hand)
static bool setupJoint(ar_hand_skeleton_joint_name_t jointName, const ar_hand_skeleton_t handSkeleton, const simd_float4x4 handTransform, QVector3D &jointPosition, QQuaternion &jointRotation)
static qsizetype getJointIndex(ar_hand_skeleton_joint_name_t jointName)
const HandJointList & getJointNameTable()
static void setInputValue(QtQuick3DXr::Hand hand, int id, const char *shortName, float value)
static void getHandPose(ar_hand_skeleton_t handSkeleton, const simd_float4x4 handTransform, QVector3D &posePosition, QQuaternion &poseRotation)
static const ActionTypeAndName VOPinchGestures[]
QQuick3DXrInputAction::Action type