Revision control

Copy as Markdown

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "../qquick3dxrinputmanager_p.h"
#include "qopenxrinputmanager_p.h"
#include "openxr/qopenxrhelpers_p.h"
#include "qquick3dxrhandinput_p.h"
#include "qquick3dxrhandmodel_p.h"
#include "qquick3dxrcontroller_p.h" //### InputAction enum
#include <QDebug>
#include <private/qquick3djoint_p.h>
#include <QtGui/qquaternion.h>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcQuick3DXr);
QQuick3DXrInputManagerPrivate::QQuick3DXrInputManagerPrivate(QQuick3DXrInputManager &manager)
: q_ptr(&manager)
{
m_handInputState[uint(Handedness::Left)] = new QQuick3DXrHandInput(this);
m_handInputState[uint(Handedness::Right)] = new QQuick3DXrHandInput(this);
}
QQuick3DXrInputManagerPrivate::~QQuick3DXrInputManagerPrivate()
{
teardown();
delete m_handInputState[uint(Handedness::Left)];
delete m_handInputState[uint(Handedness::Right)];
m_handInputState[uint(Handedness::Left)] = nullptr;
m_handInputState[uint(Handedness::Right)] = nullptr;
}
QQuick3DXrInputManagerPrivate::QXRHandComponentPath QQuick3DXrInputManagerPrivate::makeHandInputPaths(const QByteArrayView path)
{
QXRHandComponentPath res;
setPath(res.paths[uint(Handedness::Left)], "/user/hand/left/" + path);
setPath(res.paths[uint(Handedness::Right)], "/user/hand/right/" + path);
return res;
}
XrPath QQuick3DXrInputManagerPrivate::makeInputPath(const QByteArrayView path)
{
XrPath res;
setPath(res, path.toByteArray());
return res;
}
QQuick3DGeometry *QQuick3DXrInputManagerPrivate::createHandMeshGeometry(const HandMeshData &handMeshData)
{
QQuick3DGeometry *geometry = new QQuick3DGeometry();
geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
// Figure out which attributes should be used
const qsizetype expectedLength = handMeshData.vertexPositions.size();
bool hasPositions = !handMeshData.vertexPositions.isEmpty();
bool hasNormals = handMeshData.vertexNormals.size() >= expectedLength;
bool hasUV0s = handMeshData.vertexUVs.size() >= expectedLength;
bool hasJoints = handMeshData.vertexBlendIndices.size() >= expectedLength;
bool hasWeights = handMeshData.vertexBlendWeights.size() >= expectedLength;
bool hasIndexes = !handMeshData.indices.isEmpty();
int offset = 0;
if (hasPositions) {
geometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, offset, QQuick3DGeometry::Attribute::ComponentType::F32Type);
offset += 3 * sizeof(float);
}
if (hasNormals) {
geometry->addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, offset, QQuick3DGeometry::Attribute::ComponentType::F32Type);
offset += 3 * sizeof(float);
}
if (hasUV0s) {
geometry->addAttribute(QQuick3DGeometry::Attribute::TexCoordSemantic, offset, QQuick3DGeometry::Attribute::ComponentType::F32Type);
offset += 2 * sizeof(float);
}
if (hasJoints) {
geometry->addAttribute(QQuick3DGeometry::Attribute::JointSemantic, offset, QQuick3DGeometry::Attribute::ComponentType::I32Type);
offset += 4 * sizeof(qint32);
}
if (hasWeights) {
geometry->addAttribute(QQuick3DGeometry::Attribute::WeightSemantic, offset, QQuick3DGeometry::Attribute::ComponentType::F32Type);
offset += 4 * sizeof(float);
}
if (hasIndexes)
geometry->addAttribute(QQuick3DGeometry::Attribute::IndexSemantic, 0, QQuick3DGeometry::Attribute::ComponentType::U16Type);
// set up the vertex buffer
const int stride = offset;
const qsizetype bufferSize = expectedLength * stride;
geometry->setStride(stride);
QByteArray vertexBuffer;
vertexBuffer.reserve(bufferSize);
QVector3D minBounds;
QVector3D maxBounds;
auto appendFloat = [&vertexBuffer](float f) {
vertexBuffer.append(reinterpret_cast<const char *>(&f), sizeof(float));
};
auto appendInt = [&vertexBuffer](qint32 i) {
vertexBuffer.append(reinterpret_cast<const char *>(&i), sizeof(qint32));
};
for (qsizetype i = 0; i < expectedLength; ++i) {
// start writing float values to vertexBuffer
if (hasPositions) {
const QVector3D position = OpenXRHelpers::toQVector(handMeshData.vertexPositions[i]);
appendFloat(position.x());
appendFloat(position.y());
appendFloat(position.z());
minBounds.setX(qMin(minBounds.x(), position.x()));
maxBounds.setX(qMax(maxBounds.x(), position.x()));
minBounds.setY(qMin(minBounds.y(), position.y()));
maxBounds.setY(qMax(maxBounds.y(), position.y()));
minBounds.setZ(qMin(minBounds.z(), position.z()));
maxBounds.setZ(qMax(maxBounds.z(), position.z()));
}
if (hasNormals) {
const auto &normal = handMeshData.vertexNormals[i];
appendFloat(normal.x);
appendFloat(normal.y);
appendFloat(normal.z);
}
if (hasUV0s) {
const auto &uv0 = handMeshData.vertexUVs[i];
appendFloat(uv0.x);
appendFloat(uv0.y);
}
if (hasJoints) {
const auto &joint = handMeshData.vertexBlendIndices[i];
appendInt(joint.x);
appendInt(joint.y);
appendInt(joint.z);
appendInt(joint.w);
}
if (hasWeights) {
const auto &weight = handMeshData.vertexBlendWeights[i];
appendFloat(weight.x);
appendFloat(weight.y);
appendFloat(weight.z);
appendFloat(weight.w);
}
}
geometry->setBounds(minBounds, maxBounds);
geometry->setVertexData(vertexBuffer);
// Index Buffer
if (hasIndexes) {
const qsizetype indexLength = handMeshData.indices.size();
QByteArray indexBuffer;
indexBuffer.reserve(indexLength * sizeof(int16_t));
for (qsizetype i = 0; i < indexLength; ++i) {
const auto &index = handMeshData.indices[i];
indexBuffer.append(reinterpret_cast<const char *>(&index), sizeof(int16_t));
}
geometry->setIndexData(indexBuffer);
}
return geometry;
}
void QQuick3DXrInputManagerPrivate::loadBindings(QList<ControllerBindings>* controllerBindingsList)
{
// Oculus touch and HTC Vive Controllers
QList<ActionPaths> GripAimHapticSupported = {
ActionPaths::leftGripPose,
ActionPaths::leftAimPose,
ActionPaths::leftHaptic,
ActionPaths::rightGripPose,
ActionPaths::rightAimPose,
ActionPaths::rightHaptic
};
// Oculus Touch
QList<InputMapping> oculusTouchInputMapping = {
InputMapping{QQuick3DXrInputAction::Button1Pressed, InputNames::XClick, LeftHandSubPath},
InputMapping{QQuick3DXrInputAction::Button1Pressed, InputNames::AClick, RightHandSubPath},
InputMapping{QQuick3DXrInputAction::Button2Pressed, InputNames::YClick, LeftHandSubPath},
InputMapping{QQuick3DXrInputAction::Button2Pressed, InputNames::BClick, RightHandSubPath},
InputMapping{QQuick3DXrInputAction::Button1Touched, InputNames::XTouch, LeftHandSubPath},
InputMapping{QQuick3DXrInputAction::Button1Touched, InputNames::ATouch, RightHandSubPath},
InputMapping{QQuick3DXrInputAction::Button2Touched, InputNames::YTouch, LeftHandSubPath},
InputMapping{QQuick3DXrInputAction::Button2Touched, InputNames::BTouch, RightHandSubPath},
InputMapping{QQuick3DXrInputAction::ButtonMenuPressed, InputNames::MenuClick, LeftHandSubPath},
InputMapping{QQuick3DXrInputAction::ButtonSystemPressed, InputNames::SystemClick, RightHandSubPath},
InputMapping{QQuick3DXrInputAction::SqueezeValue, InputNames::SqueezeValue, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::TriggerValue, InputNames::TriggerValue, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::TriggerTouched, InputNames::TriggerTouch, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::ThumbstickX, InputNames::ThumbstickX, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::ThumbstickY, InputNames::ThumbstickY, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::ThumbstickPressed, InputNames::ThumbstickClick, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::ThumbstickTouched, InputNames::ThumbstickTouch, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::ThumbrestTouched, InputNames::ThumbrestTouch, BothHandsSubPath},
};
ControllerBindings oculusTouch{
"Oculus touch",
"/interaction_profiles/oculus/touch_controller",
oculusTouchInputMapping,
GripAimHapticSupported
};
controllerBindingsList->append(oculusTouch);
// HTC Vive controller
QList<InputMapping> viveControllerInputMapping {
InputMapping{QQuick3DXrInputAction::ButtonMenuPressed, InputNames::MenuClick, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::ButtonSystemPressed, InputNames::SystemClick, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::SqueezePressed, InputNames::SqueezeClick, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::TriggerValue, InputNames::TriggerValue, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::TriggerPressed, InputNames::TriggerClick, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::TrackpadX, InputNames::TrackpadX, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::TrackpadY, InputNames::TrackpadY, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::TrackpadPressed, InputNames::TrackpadClick, BothHandsSubPath},
InputMapping{QQuick3DXrInputAction::TrackpadTouched, InputNames::TrackpadTouch, BothHandsSubPath},
};
ControllerBindings viveController {
"Vive controller",
"/interaction_profiles/htc/vive_controller",
viveControllerInputMapping,
GripAimHapticSupported
};
controllerBindingsList->append(viveController);
// Microsoft hand interaction extension as supported by Quest 3
// TODO: there are other, very similar, extensions: XR_HTC_HAND_INTERACTION_EXTENSION_NAME and XR_EXT_HAND_INTERACTION_EXTENSION_NAME
QList<InputMapping> microsoftHandInteractionExtensionInputMapping {
InputMapping{QQuick3DXrInputAction::SqueezeValue, InputNames::SqueezeValue, BothHandsSubPath},
};
QList<ActionPaths> microsoftHandInteractionExtensionActionPaths = {
ActionPaths::leftGripPose,
ActionPaths::leftAimPose,
ActionPaths::rightGripPose,
ActionPaths::rightAimPose
};
ControllerBindings microsoftHandInteractionExtension {
"microsoftHandInteractionExtension",
"/interaction_profiles/microsoft/hand_interaction",
microsoftHandInteractionExtensionInputMapping,
microsoftHandInteractionExtensionActionPaths
};
controllerBindingsList->append(microsoftHandInteractionExtension);
#if 0 // Unused
// Microsoft MRM ### TODO
QList<InputMapping> microsoftMRMInputMapping {};
QList<ActionPaths> microsoftMRMActionPaths;
ControllerBindings microsoftMRM{
"Microsoft MRM",
"/interaction_profiles/microsoft/motion_controller",
microsoftMRMInputMapping,
microsoftMRMActionPaths
};
controllerBindingsList->append(microsoftMRM);
// Valve Index ### TODO
QList<InputMapping> valveIndexInputMapping {};
QList<ActionPaths> valveIndexActionPaths;
ControllerBindings valveIndex{
"Valve Index",
"/interaction_profiles/valve/index_controller",
valveIndexInputMapping,
valveIndexActionPaths
};
controllerBindingsList->append(valveIndex);
#endif // Unused
}
void QQuick3DXrInputManagerPrivate::setUpBindings(QList<ControllerBindings>* controllerBindingsList, QMap<InputNames, QXRHandComponentPath>* handComponentPaths)
{
QMap<ActionPaths, XrActionSuggestedBinding> actionPaths;
// Hand Left
XrPath leftGripPose; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
XrPath leftAimPose; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
XrPath leftHaptic; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
setPath(leftGripPose, "/user/hand/left/input/grip/pose");
setPath(leftAimPose, "/user/hand/left/input/aim/pose");
setPath(leftHaptic, "/user/hand/left/output/haptic");
actionPaths.insert(ActionPaths::leftGripPose, {m_handActions.gripPoseAction, leftGripPose});
actionPaths.insert(ActionPaths::leftAimPose, {m_handActions.aimPoseAction, leftAimPose});
actionPaths.insert(ActionPaths::leftHaptic, {m_handActions.hapticAction, leftHaptic});
// Hand Right
XrPath rightGripPose; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
XrPath rightAimPose; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
XrPath rightHaptic; // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
setPath(rightGripPose, "/user/hand/right/input/grip/pose");
setPath(rightAimPose, "/user/hand/right/input/aim/pose");
setPath(rightHaptic, "/user/hand/right/output/haptic");
actionPaths.insert(ActionPaths::rightGripPose, {m_handActions.gripPoseAction, rightGripPose});
actionPaths.insert(ActionPaths::rightAimPose, {m_handActions.aimPoseAction, rightAimPose});
actionPaths.insert(ActionPaths::rightHaptic, {m_handActions.hapticAction, rightHaptic});
for (int i = 0; i < controllerBindingsList->size(); i++) {
ControllerBindings controllerBindings = controllerBindingsList->at(i);
if (controllerBindings.profileMappingDefs.size() == 0 || controllerBindings.supportedActionPaths.size() == 0)
continue;
XrPath profilePath;
setPath(profilePath, controllerBindings.profilePath);
std::vector<XrActionSuggestedBinding> bindings {};
for (const auto &path : std::as_const(controllerBindings.supportedActionPaths)) {
bindings.push_back(actionPaths.value(path));
}
for (const auto &[actionId, path, selector] : std::as_const(controllerBindings.profileMappingDefs)) {
if (selector & LeftHandSubPath)
bindings.push_back({m_inputActions[actionId], handComponentPaths->value(path).paths[uint(Handedness::Left)]});
if (selector & RightHandSubPath)
bindings.push_back({m_inputActions[actionId], handComponentPaths->value(path).paths[uint(Handedness::Right)]});
}
XrInteractionProfileSuggestedBinding suggestedBindings{};
suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
suggestedBindings.interactionProfile = profilePath;
suggestedBindings.suggestedBindings = bindings.data();
suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size();
if (!checkXrResult(xrSuggestInteractionProfileBindings(m_instance, &suggestedBindings)))
qWarning() << "Failed to get suggested interaction profile bindings for " << controllerBindings.profileName;
}
}
void QQuick3DXrInputManagerPrivate::init(XrInstance instance, XrSession session)
{
if (m_initialized) {
qWarning() << "QQuick3DXrInputManager: Trying to initialize an already initialized session";
teardown();
}
m_instance = instance;
m_session = session;
setupHandTracking();
setupActions();
QMap<InputNames, QXRHandComponentPath> handComponentPaths;
handComponentPaths.insert(InputNames::AClick, makeHandInputPaths("input/a/click")); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
handComponentPaths.insert(InputNames::BClick, makeHandInputPaths("input/b/click")); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
handComponentPaths.insert(InputNames::ATouch, makeHandInputPaths("input/a/touch")); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
handComponentPaths.insert(InputNames::BTouch, makeHandInputPaths("input/b/touch")); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
handComponentPaths.insert(InputNames::XClick, makeHandInputPaths("input/x/click")); // OCULUS_TOUCH (left)
handComponentPaths.insert(InputNames::YClick, makeHandInputPaths("input/y/click")); // OCULUS_TOUCH (left)
handComponentPaths.insert(InputNames::XTouch, makeHandInputPaths("input/x/touch")); // OCULUS_TOUCH (left)
handComponentPaths.insert(InputNames::YTouch, makeHandInputPaths("input/y/touch")); // OCULUS_TOUCH (left)
handComponentPaths.insert(InputNames::MenuClick, makeHandInputPaths("input/menu/click")); // OCULUS_TOUCH (left) | MICROSOFT_MRM (right + left) | HTC_VIVE (right + left)
handComponentPaths.insert(InputNames::SystemClick, makeHandInputPaths("input/system/click")); // OCULUS_TOUCH (right) | VALVE_INDEX (right + left) | HTC_VIVE (right + left)
handComponentPaths.insert(InputNames::SystemTouch, makeHandInputPaths("input/system/touch")); // VALVE_INDEX (right + left)
handComponentPaths.insert(InputNames::SqueezeValue, makeHandInputPaths("input/squeeze/value")); // right + left: OCULUS_TOUCH | VALVE_INDEX
handComponentPaths.insert(InputNames::SqueezeForce, makeHandInputPaths("input/squeeze/force")); // right + left: VALVE_INDEX
handComponentPaths.insert(InputNames::SqueezeClick, makeHandInputPaths("input/squeeze/click")); // right + left: MICROSOFT_MRM | HTC_VIVE
handComponentPaths.insert(InputNames::TriggerValue, makeHandInputPaths("input/trigger/value")); // right + left: OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
handComponentPaths.insert(InputNames::TriggerTouch, makeHandInputPaths("input/trigger/touch")); // right + left: OCULUS_TOUCH | VALVE_INDEX
handComponentPaths.insert(InputNames::TriggerClick, makeHandInputPaths("input/trigger/click")); // right + left: VALVE_INDEX | HTC_VIVE
handComponentPaths.insert(InputNames::ThumbstickX, makeHandInputPaths("input/thumbstick/x")); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
handComponentPaths.insert(InputNames::ThumbstickY, makeHandInputPaths("input/thumbstick/y")); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
handComponentPaths.insert(InputNames::ThumbstickClick, makeHandInputPaths("input/thumbstick/click")); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
handComponentPaths.insert(InputNames::ThumbstickTouch, makeHandInputPaths("input/thumbstick/touch")); // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left)
handComponentPaths.insert(InputNames::ThumbrestTouch, makeHandInputPaths("input/thumbrest/touch")); // OCULUS_TOUCH (right + left)
handComponentPaths.insert(InputNames::TrackpadX, makeHandInputPaths("input/trackpad/x")); // right + left: VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
handComponentPaths.insert(InputNames::TrackpadY, makeHandInputPaths("input/trackpad/y")); // right + left: VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
handComponentPaths.insert(InputNames::TrackpadForce, makeHandInputPaths("input/trackpad/force")); // right + left: VALVE_INDEX
handComponentPaths.insert(InputNames::TrackpadClick, makeHandInputPaths("input/trackpad/click")); // right + left: VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
handComponentPaths.insert(InputNames::TrackpadTouch, makeHandInputPaths("input/trackpad/touch")); // right + left: MICROSOFT_MRM | HTC_VIVE
// Bindings
QList<ControllerBindings> controllerBindingsList;
loadBindings(&controllerBindingsList);
setUpBindings(&controllerBindingsList, &handComponentPaths);
// Setup Action Spaces
XrActionSpaceCreateInfo actionSpaceInfo{};
actionSpaceInfo.type = XR_TYPE_ACTION_SPACE_CREATE_INFO;
actionSpaceInfo.action = m_handActions.gripPoseAction;
actionSpaceInfo.poseInActionSpace.orientation.w = 1.0f;
//actionSpaceInfo.poseInActionSpace.orientation.y = 1.0f;
actionSpaceInfo.subactionPath = m_handSubactionPath[0];
if (!checkXrResult(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_handGripSpace[0])))
qWarning("Failed to create action space for handGripSpace[0]");
actionSpaceInfo.subactionPath = m_handSubactionPath[1];
if (!checkXrResult(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_handGripSpace[1])))
qWarning("Failed to create action space for handGripSpace[1]");
actionSpaceInfo.action = m_handActions.aimPoseAction;
actionSpaceInfo.subactionPath = m_handSubactionPath[0];
if (!checkXrResult(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_handAimSpace[0])))
qWarning("Failed to create action space for handAimSpace[0]");
actionSpaceInfo.subactionPath = m_handSubactionPath[1];
if (!checkXrResult(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_handAimSpace[1])))
qWarning("Failed to create action space for handAimSpace[1]");
// Attach Action set to session
XrSessionActionSetsAttachInfo attachInfo{};
attachInfo.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO;
attachInfo.countActionSets = 1;
attachInfo.actionSets = &m_actionSet;
if (!checkXrResult(xrAttachSessionActionSets(m_session, &attachInfo)))
qWarning("Failed to attach action sets to session");
m_initialized = true;
}
void QQuick3DXrInputManagerPrivate::teardown()
{
if (!m_initialized)
return;
m_initialized = false;
xrDestroySpace(m_handGripSpace[0]);
xrDestroySpace(m_handGripSpace[1]);
xrDestroySpace(m_handAimSpace[0]);
xrDestroySpace(m_handAimSpace[1]);
destroyActions();
if (xrDestroyHandTrackerEXT_) {
xrDestroyHandTrackerEXT_(handTracker[uint(Handedness::Left)]);
xrDestroyHandTrackerEXT_(handTracker[uint(Handedness::Right)]);
}
m_instance = {XR_NULL_HANDLE};
m_session = {XR_NULL_HANDLE};
}
QQuick3DXrInputManagerPrivate *QQuick3DXrInputManagerPrivate::get(QQuick3DXrInputManager *inputManager)
{
QSSG_ASSERT(inputManager != nullptr, return nullptr);
return inputManager->d_func();
}
void QQuick3DXrInputManagerPrivate::pollActions()
{
if (!m_initialized)
return;
// Sync Actions
const XrActiveActionSet activeActionSet{m_actionSet, XR_NULL_PATH};
XrActionsSyncInfo syncInfo{};
syncInfo.type = XR_TYPE_ACTIONS_SYNC_INFO;
syncInfo.countActiveActionSets = 1;
syncInfo.activeActionSets = &activeActionSet;
XrResult result = xrSyncActions(m_session, &syncInfo);
if (!(result == XR_SUCCESS ||
result == XR_SESSION_LOSS_PENDING ||
result == XR_SESSION_NOT_FOCUSED))
{
if (!checkXrResult(result)) {
qWarning("xrSyncActions failed");
return;
}
}
// Hands
XrActionStateGetInfo getInfo{};
getInfo.type = XR_TYPE_ACTION_STATE_GET_INFO;
for (auto handedness : {Handedness::Left, Handedness::Right}) {
getInfo.subactionPath = m_handSubactionPath[uint(handedness)];
auto &inputState = m_handInputState[uint(handedness)];
for (const auto &def : std::as_const(m_handInputActionDefs)) {
getInfo.action = m_inputActions[def.id];
switch (def.type) {
case XR_ACTION_TYPE_BOOLEAN_INPUT: {
XrActionStateBoolean boolValue{};
boolValue.type = XR_TYPE_ACTION_STATE_BOOLEAN;
if (checkXrResult(xrGetActionStateBoolean(m_session, &getInfo, &boolValue))) {
if (boolValue.isActive && boolValue.changedSinceLastSync) {
//qDebug() << "ACTION" << i << def.shortName << bool(boolValue.currentState);
setInputValue(handedness, def.id, def.shortName, float(boolValue.currentState));
}
} else {
qWarning("Failed to get action state for bool hand input");
}
break;
}
case XR_ACTION_TYPE_FLOAT_INPUT: {
XrActionStateFloat floatValue{};
floatValue.type = XR_TYPE_ACTION_STATE_FLOAT;
if (checkXrResult(xrGetActionStateFloat(m_session, &getInfo, &floatValue))) {
if (floatValue.isActive && floatValue.changedSinceLastSync) {
//qDebug() << "ACTION" << i << def.shortName << floatValue.currentState;
setInputValue(handedness, def.id, def.shortName, float(floatValue.currentState));
}
} else {
qWarning("Failed to get action state for float hand input");
}
break;
}
case XR_ACTION_TYPE_VECTOR2F_INPUT:
case XR_ACTION_TYPE_POSE_INPUT:
case XR_ACTION_TYPE_VIBRATION_OUTPUT:
case XR_ACTION_TYPE_MAX_ENUM:
break;
}
}
// Get pose activity status
getInfo.action = m_handActions.gripPoseAction;
XrActionStatePose poseState{};
poseState.type = XR_TYPE_ACTION_STATE_POSE;
if (checkXrResult(xrGetActionStatePose(m_session, &getInfo, &poseState)))
inputState->setIsActive(poseState.isActive);
else
qWarning("Failed to get action state pose");
// XrAction gripPoseAction{XR_NULL_HANDLE};
// XrAction aimPoseAction{XR_NULL_HANDLE};
// XrAction hapticAction{XR_NULL_HANDLE};
const QList<QPointer<QQuick3DXrHapticFeedback>> hapticOutputData = QQuick3DXrActionMapper::getHapticEffects(static_cast<QtQuick3DXr::Controller>(handedness));
for (auto &hapticFeedback : hapticOutputData) {
const bool triggered = hapticFeedback->testAndClear();
if (triggered) {
if (auto *hapticEffect = qobject_cast<QQuick3DXrSimpleHapticEffect*>(hapticFeedback->hapticEffect())) {
XrHapticVibration vibration {XR_TYPE_HAPTIC_VIBRATION, nullptr, 0, 0, 0};
vibration.amplitude = hapticEffect->amplitude();
vibration.duration = hapticEffect->duration() * 1000000; // Change from milliseconds to nanoseconds
vibration.frequency = hapticEffect->frequency();
XrHapticActionInfo hapticActionInfo {XR_TYPE_HAPTIC_ACTION_INFO, nullptr, m_handActions.hapticAction, m_handSubactionPath[uint(handedness)]};
if (!checkXrResult(xrApplyHapticFeedback(m_session, &hapticActionInfo, (const XrHapticBaseHeader*)&vibration))) {
qWarning("Failed to trigger haptic feedback");
}
}
}
}
}
}
void QQuick3DXrInputManagerPrivate::updatePoses(XrTime predictedDisplayTime, XrSpace appSpace)
{
// Update the Hands pose
for (auto poseSpace : {HandPoseSpace::AimPose, HandPoseSpace::GripPose}) {
for (auto handedness : {Handedness::Left, Handedness::Right}) {
if (!isPoseInUse(handedness, poseSpace))
continue;
XrSpaceLocation spaceLocation{};
spaceLocation.type = XR_TYPE_SPACE_LOCATION;
XrResult res;
res = xrLocateSpace(handSpace(handedness, poseSpace), appSpace, predictedDisplayTime, &spaceLocation);
// qDebug() << "LOCATE SPACE hand:" << hand << "res" << res << "flags" << spaceLocation.locationFlags
// << "active" << m_handInputState[hand]->isActive()
// << "Pos" << spaceLocation.pose.position.x << spaceLocation.pose.position.y << spaceLocation.pose.position.z;
m_validAimStateFromUpdatePoses[uint(handedness)] = poseSpace == HandPoseSpace::AimPose
&& XR_UNQUALIFIED_SUCCESS(res) && (spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT)
&& (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT); // ### Workaround for Quest issue with hand interaction aim pose
if (XR_UNQUALIFIED_SUCCESS(res)) {
if ((spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 &&
(spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0) {
// Update hand transform
setPosePositionAndRotation(handedness, poseSpace,
QVector3D(spaceLocation.pose.position.x,
spaceLocation.pose.position.y,
spaceLocation.pose.position.z) * 100.0f,
QQuaternion(spaceLocation.pose.orientation.w,
spaceLocation.pose.orientation.x,
spaceLocation.pose.orientation.y,
spaceLocation.pose.orientation.z));
}
} else {
// Tracking loss is expected when the hand is not active so only log a message
// if the hand is active.
if (isHandActive(handedness)) {
const char* handName[] = {"left", "right"};
qCDebug(lcQuick3DXr, "Unable to locate %s hand action space in app space: %d", handName[uint(handedness)], res);
}
}
}
}
}
void QQuick3DXrInputManagerPrivate::updateHandtracking(XrTime predictedDisplayTime, XrSpace appSpace, bool aimExtensionEnabled)
{
if (xrLocateHandJointsEXT_) {
XrHandTrackingAimStateFB aimState[2] = {{}, {}}; // Only used when aim extension is enabled
XrHandJointVelocitiesEXT velocities[2]{{}, {}};
XrHandJointLocationsEXT locations[2]{{}, {}};
XrHandJointsLocateInfoEXT locateInfo[2] = {{}, {}};
for (auto handedness : {Handedness::Left, Handedness::Right}) {
if (handTracker[uint(handedness)] == XR_NULL_HANDLE)
continue;
aimState[uint(handedness)].type = XR_TYPE_HAND_TRACKING_AIM_STATE_FB;
velocities[uint(handedness)].type = XR_TYPE_HAND_JOINT_VELOCITIES_EXT;
velocities[uint(handedness)].jointCount = XR_HAND_JOINT_COUNT_EXT;
velocities[uint(handedness)].jointVelocities = jointVelocities[uint(handedness)];
velocities[uint(handedness)].next = aimExtensionEnabled ? &aimState[uint(handedness)] : nullptr;
locations[uint(handedness)].type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT;
locations[uint(handedness)].next = &velocities[uint(handedness)];
locations[uint(handedness)].jointCount = XR_HAND_JOINT_COUNT_EXT;
locations[uint(handedness)].jointLocations = jointLocations[uint(handedness)];
locateInfo[uint(handedness)].type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT;
locateInfo[uint(handedness)].baseSpace = appSpace;
locateInfo[uint(handedness)].time = predictedDisplayTime;
if (!checkXrResult(xrLocateHandJointsEXT_(handTracker[uint(handedness)], &locateInfo[uint(handedness)], &locations[uint(handedness)])))
qWarning("Failed to locate hand joints for hand tracker");
QList<QVector3D> jp;
jp.reserve(XR_HAND_JOINT_COUNT_EXT);
QList<QQuaternion> jr;
jr.reserve(XR_HAND_JOINT_COUNT_EXT);
for (uint i = 0; i < locations[uint(handedness)].jointCount; ++i) {
auto &pose = jointLocations[uint(handedness)][i].pose;
jp.append(OpenXRHelpers::toQVector(pose.position));
jr.append(OpenXRHelpers::toQQuaternion(pose.orientation));
}
m_handInputState[uint(handedness)]->setJointPositionsAndRotations(jp, jr);
m_handInputState[uint(handedness)]->setIsHandTrackingActive(locations[uint(handedness)].isActive);
}
if (aimExtensionEnabled) {
// Finger pinch handling
for (auto handedness : {Handedness::Left, Handedness::Right}) {
const uint state = aimState[uint(handedness)].status;
const uint oldState = m_aimStateFlags[uint(handedness)];
auto updateState = [&](const char *name, QQuick3DXrInputAction::Action id, uint flag) {
if ((state & flag) != (oldState & flag))
setInputValue(handedness, id, name, float(!!(state & flag)));
};
updateState("index_pinch", QQuick3DXrInputAction::IndexFingerPinch, XR_HAND_TRACKING_AIM_INDEX_PINCHING_BIT_FB);
updateState("middle_pinch", QQuick3DXrInputAction::MiddleFingerPinch, XR_HAND_TRACKING_AIM_MIDDLE_PINCHING_BIT_FB);
updateState("ring_pinch", QQuick3DXrInputAction::RingFingerPinch, XR_HAND_TRACKING_AIM_RING_PINCHING_BIT_FB);
updateState("little_pinch", QQuick3DXrInputAction::LittleFingerPinch, XR_HAND_TRACKING_AIM_LITTLE_PINCHING_BIT_FB);
updateState("hand_tracking_menu_press", QQuick3DXrInputAction::HandTrackingMenuPress, XR_HAND_TRACKING_AIM_MENU_PRESSED_BIT_FB);
m_aimStateFlags[uint(handedness)] = state;
}
// ### Workaround for Quest issue with hand interaction aim pose
for (auto handedness : {Handedness::Left, Handedness::Right}) {
if (isPoseInUse(handedness, HandPoseSpace::AimPose) && !m_validAimStateFromUpdatePoses[uint(handedness)]) {
if ((aimState[uint(handedness)].status & XR_HAND_TRACKING_AIM_VALID_BIT_FB)) {
setPosePositionAndRotation(handedness, HandPoseSpace::AimPose,
QVector3D(aimState[uint(handedness)].aimPose.position.x,
aimState[uint(handedness)].aimPose.position.y,
aimState[uint(handedness)].aimPose.position.z) * 100.0f,
QQuaternion(aimState[uint(handedness)].aimPose.orientation.w,
aimState[uint(handedness)].aimPose.orientation.x,
aimState[uint(handedness)].aimPose.orientation.y,
aimState[uint(handedness)].aimPose.orientation.z));
m_handInputState[uint(handedness)]->setIsActive(true); // TODO: clean up
}
}
}
}
}
}
void QQuick3DXrInputManagerPrivate::setupHandTracking()
{
OpenXRHelpers::resolveXrFunction(
m_instance,
"xrCreateHandTrackerEXT",
(PFN_xrVoidFunction*)(&xrCreateHandTrackerEXT_));
OpenXRHelpers::resolveXrFunction(
m_instance,
"xrDestroyHandTrackerEXT",
(PFN_xrVoidFunction*)(&xrDestroyHandTrackerEXT_));
OpenXRHelpers::resolveXrFunction(
m_instance,
"xrLocateHandJointsEXT",
(PFN_xrVoidFunction*)(&xrLocateHandJointsEXT_));
OpenXRHelpers::resolveXrFunction(
m_instance,
"xrGetHandMeshFB",
(PFN_xrVoidFunction*)(&xrGetHandMeshFB_));
if (xrCreateHandTrackerEXT_) {
XrHandTrackerCreateInfoEXT createInfo{};
createInfo.type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT;
createInfo.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT;
createInfo.hand = XR_HAND_LEFT_EXT;
if (!checkXrResult(xrCreateHandTrackerEXT_(m_session, &createInfo, &handTracker[uint(QtQuick3DXr::Handedness::Left)])))
qWarning("Failed to create left hand tracker");
createInfo.hand = XR_HAND_RIGHT_EXT;
if (!checkXrResult(xrCreateHandTrackerEXT_(m_session, &createInfo, &handTracker[uint(QtQuick3DXr::Handedness::Right)])))
qWarning("Failed to create right hand tracker");
}
if (xrGetHandMeshFB_) {
for (auto handedness : {Handedness::Left, Handedness::Right}) {
if (queryHandMesh(handedness))
createHandModelData(handedness);
}
}
}
bool QQuick3DXrInputManagerPrivate::queryHandMesh(Handedness handedness)
{
XrHandTrackingMeshFB mesh {};
mesh.type = XR_TYPE_HAND_TRACKING_MESH_FB;
// Left hand
if (!checkXrResult(xrGetHandMeshFB_(handTracker[uint(handedness)], &mesh))) {
qWarning("Failed to query hand mesh info.");
return false;
}
mesh.jointCapacityInput = mesh.jointCountOutput;
mesh.vertexCapacityInput = mesh.vertexCountOutput;
mesh.indexCapacityInput = mesh.indexCountOutput;
m_handMeshData[uint(handedness)].vertexPositions.resize(mesh.vertexCapacityInput);
m_handMeshData[uint(handedness)].vertexNormals.resize(mesh.vertexCapacityInput);
m_handMeshData[uint(handedness)].vertexUVs.resize(mesh.vertexCapacityInput);
m_handMeshData[uint(handedness)].vertexBlendIndices.resize(mesh.vertexCapacityInput);
m_handMeshData[uint(handedness)].vertexBlendWeights.resize(mesh.vertexCapacityInput);
m_handMeshData[uint(handedness)].indices.resize(mesh.indexCapacityInput);
mesh.jointBindPoses = m_handMeshData[uint(handedness)].jointBindPoses;
mesh.jointParents = m_handMeshData[uint(handedness)].jointParents;
mesh.jointRadii = m_handMeshData[uint(handedness)].jointRadii;
mesh.vertexPositions = m_handMeshData[uint(handedness)].vertexPositions.data();
mesh.vertexNormals = m_handMeshData[uint(handedness)].vertexNormals.data();
mesh.vertexUVs = m_handMeshData[uint(handedness)].vertexUVs.data();
mesh.vertexBlendIndices = m_handMeshData[uint(handedness)].vertexBlendIndices.data();
mesh.vertexBlendWeights = m_handMeshData[uint(handedness)].vertexBlendWeights.data();
mesh.indices = m_handMeshData[uint(handedness)].indices.data();
if (!checkXrResult(xrGetHandMeshFB_(handTracker[uint(handedness)], &mesh))) {
qWarning("Failed to get hand mesh data.");
return false;
}
return true;
};
void QQuick3DXrInputManagerPrivate::setupActions()
{
m_handInputActionDefs = {
{ QQuick3DXrInputAction::Button1Pressed, "b1_pressed", "Button 1 Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::Button1Touched, "b1_touched", "Button 1 Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::Button2Pressed, "b2_pressed", "Button 2 Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::Button2Touched, "b2_touched", "Button 2 Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::ButtonMenuPressed, "bmenu_pressed", "Button Menu Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::ButtonMenuTouched, "bmenu_touched", "Button Menu Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::ButtonSystemPressed, "bsystem_pressed", "Button System Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::ButtonSystemTouched, "bsystem_touched", "Button System Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::SqueezeValue, "squeeze_value", "Squeeze Value", XR_ACTION_TYPE_FLOAT_INPUT },
{ QQuick3DXrInputAction::SqueezeForce, "squeeze_force", "Squeeze Force", XR_ACTION_TYPE_FLOAT_INPUT },
{ QQuick3DXrInputAction::SqueezePressed, "squeeze_pressed", "Squeeze Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::TriggerValue, "trigger_value", "Trigger Value", XR_ACTION_TYPE_FLOAT_INPUT },
{ QQuick3DXrInputAction::TriggerPressed, "trigger_pressed", "Trigger Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::TriggerTouched, "trigger_touched", "Trigger Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::ThumbstickX, "thumbstick_x", "Thumbstick X", XR_ACTION_TYPE_FLOAT_INPUT },
{ QQuick3DXrInputAction::ThumbstickY, "thumbstick_y", "Thumbstick Y", XR_ACTION_TYPE_FLOAT_INPUT },
{ QQuick3DXrInputAction::ThumbstickPressed, "thumbstick_pressed", "Thumbstick Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::ThumbstickTouched, "thumbstick_touched", "Thumbstick Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::ThumbrestTouched, "thumbrest_touched", "Thumbrest Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::TrackpadX, "trackpad_x", "Trackpad X", XR_ACTION_TYPE_FLOAT_INPUT },
{ QQuick3DXrInputAction::TrackpadY, "trackpad_y", "Trackpad Y", XR_ACTION_TYPE_FLOAT_INPUT },
{ QQuick3DXrInputAction::TrackpadForce, "trackpad_force", "Trackpad Force", XR_ACTION_TYPE_FLOAT_INPUT },
{ QQuick3DXrInputAction::TrackpadTouched, "trackpad_touched", "Trackpad Touched", XR_ACTION_TYPE_BOOLEAN_INPUT },
{ QQuick3DXrInputAction::TrackpadPressed, "trackpad_pressed", "Trackpad Pressed", XR_ACTION_TYPE_BOOLEAN_INPUT }
};
// Create an action set.
{
XrActionSetCreateInfo actionSetInfo{};
actionSetInfo.type = XR_TYPE_ACTION_SET_CREATE_INFO;
strcpy(actionSetInfo.actionSetName, "gameplay");
strcpy(actionSetInfo.localizedActionSetName, "Gameplay");
actionSetInfo.priority = 0;
if (!checkXrResult(xrCreateActionSet(m_instance, &actionSetInfo, &m_actionSet)))
qWarning("Failed to create gameplay action set");
}
// Create Hand Actions
setPath(m_handSubactionPath[0], "/user/hand/left");
setPath(m_handSubactionPath[1], "/user/hand/right");
for (const auto &def : std::as_const(m_handInputActionDefs)) {
createAction(def.type,
def.shortName,
def.localizedName,
2,
m_handSubactionPath,
m_inputActions[def.id]);
}
createAction(XR_ACTION_TYPE_VIBRATION_OUTPUT,
"vibrate_hand",
"Vibrate Hand",
2,
m_handSubactionPath,
m_handActions.hapticAction);
createAction(XR_ACTION_TYPE_POSE_INPUT,
"hand_grip_pose",
"Hand Grip Pose",
2,
m_handSubactionPath,
m_handActions.gripPoseAction);
createAction(XR_ACTION_TYPE_POSE_INPUT,
"hand_aim_pose",
"Hand Aim Pose",
2,
m_handSubactionPath,
m_handActions.aimPoseAction);
}
void QQuick3DXrInputManagerPrivate::destroyActions()
{
for (auto &action : m_inputActions) {
if (action)
xrDestroyAction(action);
}
xrDestroyAction(m_handActions.gripPoseAction);
xrDestroyAction(m_handActions.aimPoseAction);
xrDestroyAction(m_handActions.hapticAction);
xrDestroyActionSet(m_actionSet);
}
bool QQuick3DXrInputManagerPrivate::checkXrResult(const XrResult &result)
{
return OpenXRHelpers::checkXrResult(result, m_instance);
}
void QQuick3DXrInputManagerPrivate::setPath(XrPath &path, const QByteArray &pathString)
{
if (!checkXrResult(xrStringToPath(m_instance, pathString.constData(), &path)))
qWarning("xrStringToPath failed");
}
void QQuick3DXrInputManagerPrivate::createAction(XrActionType type,
const char *name,
const char *localizedName,
int numSubactions,
XrPath *subactionPath,
XrAction &action)
{
XrActionCreateInfo actionInfo{};
actionInfo.type = XR_TYPE_ACTION_CREATE_INFO;
actionInfo.actionType = type;
strcpy(actionInfo.actionName, name);
strcpy(actionInfo.localizedActionName, localizedName);
actionInfo.countSubactionPaths = quint32(numSubactions);
actionInfo.subactionPaths = subactionPath;
if (!checkXrResult(xrCreateAction(m_actionSet, &actionInfo, &action)))
qCDebug(lcQuick3DXr) << "xrCreateAction failed. Name:" << name << "localizedName:" << localizedName;
}
void QQuick3DXrInputManagerPrivate::getBoolInputState(XrActionStateGetInfo &getInfo, const XrAction &action, std::function<void(bool)> setter)
{
getInfo.action = action;
XrActionStateBoolean boolValue{};
boolValue.type = XR_TYPE_ACTION_STATE_BOOLEAN;
if (checkXrResult(xrGetActionStateBoolean(m_session, &getInfo, &boolValue))) {
if (boolValue.isActive == XR_TRUE)
setter(bool(boolValue.currentState));
} else {
qWarning("Failed to get action state: bool");
}
}
void QQuick3DXrInputManagerPrivate::getFloatInputState(XrActionStateGetInfo &getInfo, const XrAction &action, std::function<void(float)> setter)
{
getInfo.action = action;
XrActionStateFloat floatValue{};
floatValue.type = XR_TYPE_ACTION_STATE_FLOAT;
if (checkXrResult(xrGetActionStateFloat(m_session, &getInfo, &floatValue))) {
if (floatValue.isActive == XR_TRUE)
setter(float(floatValue.currentState));
} else {
qWarning("Failed to get action state: float");
}
}
XrSpace QQuick3DXrInputManagerPrivate::handSpace(QQuick3DXrInputManagerPrivate::Handedness handedness, HandPoseSpace poseSpace)
{
if (poseSpace == HandPoseSpace::GripPose)
return m_handGripSpace[uint(handedness)];
else
return m_handAimSpace[uint(handedness)];
}
bool QQuick3DXrInputManagerPrivate::isHandActive(QQuick3DXrInputManagerPrivate::Handedness handedness)
{
return m_handInputState[uint(handedness)]->isActive();
}
bool QQuick3DXrInputManagerPrivate::isHandTrackerActive(Handedness handedness)
{
return m_handInputState[uint(handedness)]->isHandTrackingActive();
}
void QQuick3DXrInputManagerPrivate::setPosePositionAndRotation(Handedness handedness, HandPoseSpace poseSpace, const QVector3D &position, const QQuaternion &rotation)
{
for (auto *controller : std::as_const(m_controllers)) {
if (QtQuick3DXr::handednessForController(static_cast<QtQuick3DXr::Controller>(controller->controller())) == handedness && static_cast<QtQuick3DXr::HandPoseSpace>(controller->poseSpace()) == poseSpace) {
controller->setPosition(position);
controller->setRotation(rotation);
}
}
}
void QQuick3DXrInputManagerPrivate::setInputValue(Handedness handedness, int id, const char *shortName, float value)
{
QSSG_ASSERT(uint(handedness) < 2, handedness = Handedness::Left);
QQuick3DXrActionMapper::handleInput(QQuick3DXrInputAction::Action(id), static_cast<QtQuick3DXr::Controller>(handedness), shortName, value);
}
QQuick3DXrHandInput *QQuick3DXrInputManagerPrivate::leftHandInput() const
{
return m_handInputState[uint(Handedness::Left)];
}
QQuick3DXrHandInput *QQuick3DXrInputManagerPrivate::rightHandInput() const
{
return m_handInputState[uint(Handedness::Right)];
}
static inline QMatrix4x4 transformMatrix(const QVector3D &position, const QQuaternion &rotation)
{
QMatrix4x4 transform = QMatrix4x4{rotation.toRotationMatrix()};
transform(0, 3) += position[0];
transform(1, 3) += position[1];
transform(2, 3) += position[2];
return transform;
}
void QQuick3DXrInputManagerPrivate::setupHandModelInternal(QQuick3DXrHandModel *model, Handedness handedness)
{
QQuick3DGeometry *geometry = m_handGeometryData[uint(handedness)].geometry;
if (!geometry)
return;
model->setGeometry(geometry);
QQuick3DSkin *skin = new QQuick3DSkin(model);
auto jointListProp = skin->joints();
QList<QMatrix4x4> inverseBindPoses;
inverseBindPoses.reserve(XR_HAND_JOINT_COUNT_EXT);
const auto &handMeshData = m_handMeshData[uint(handedness)];
for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; ++i) {
const auto &pose = handMeshData.jointBindPoses[i];
const QVector3D pos = OpenXRHelpers::toQVector(pose.position);
const QQuaternion rot = OpenXRHelpers::toQQuaternion(pose.orientation);
inverseBindPoses.append(transformMatrix(pos, rot).inverted());
QQuick3DNode *joint = new QQuick3DNode(model);
joint->setPosition(pos);
joint->setRotation(rot);
jointListProp.append(&jointListProp, joint);
}
skin->setInverseBindPoses(inverseBindPoses);
model->setSkin(skin);
}
void QQuick3DXrInputManagerPrivate::setupHandModel(QQuick3DXrHandModel *model)
{
QSSG_ASSERT(model != nullptr, return);
if (model->geometry() != nullptr || model->skin() != nullptr) {
qWarning() << "Hand model already has geometry or skin set.";
return;
}
auto controller = static_cast<QtQuick3DXr::Controller>(model->hand());
if (controller == QtQuick3DXr::Controller::UnknownController)
qWarning() << "No matching hand tracker input found for hand model.";
else if (controller == QtQuick3DXr::Controller::LeftHand)
setupHandModelInternal(model, Handedness::Left);
else if (controller == QtQuick3DXr::Controller::RightHand)
setupHandModelInternal(model, Handedness::Right);
}
// Used both to add a new controller, and notify that an existing one has changed
void QQuick3DXrInputManagerPrivate::registerController(QQuick3DXrController *controller)
{
m_poseUsageDirty = true;
if (static_cast<QtQuick3DXr::Controller>(controller->controller()) == QtQuick3DXr::Controller::UnknownController) {
m_controllers.remove(controller);
return;
}
// No point in checking whether it's already in the set: that's just as expensive as inserting
m_controllers.insert(controller);
}
void QQuick3DXrInputManagerPrivate::unregisterController(QQuick3DXrController *controller)
{
m_poseUsageDirty = m_controllers.remove(controller);
}
bool QQuick3DXrInputManagerPrivate::isPoseInUse(Handedness handedness, HandPoseSpace poseSpace)
{
QSSG_ASSERT(uint(handedness) < 2 && uint(poseSpace) < 2, return false);
if (m_poseUsageDirty) {
std::fill_n(&m_poseInUse[0][0], 4, false);
for (const auto *controller : std::as_const(m_controllers)) {
m_poseInUse[uint(controller->controller())][uint(controller->poseSpace())] = true;
}
m_poseUsageDirty = false;
}
return m_poseInUse[uint(handedness)][uint(poseSpace)];
}
void QQuick3DXrInputManagerPrivate::createHandModelData(Handedness handedness)
{
const auto &handMeshData = m_handMeshData[uint(handedness)];
auto &geometry = m_handGeometryData[uint(handedness)].geometry;
delete geometry;
geometry = createHandMeshGeometry(handMeshData);
}
QT_END_NAMESPACE