//// Created by Bradley Austin Davis on 2018/11/15// Copyright 2013-2018 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "OculusMobileControllerManager.h" #include <array> #include <ui-plugins/PluginContainer.h>#include <controllers/UserInputMapper.h>#include <controllers/StandardControls.h>#include <input-plugins/KeyboardMouseDevice.h> #include <PerfStat.h>#include <PathUtils.h>#include <NumericalConstants.h>#include <StreamUtils.h>#include <VrApi.h>#include <VrApi_Input.h>#include <ovr/Helpers.h> #include "Logging.h"#include <ovr/VrHandler.h> const char* OculusMobileControllerManager::NAME = "Oculus";const quint64 LOST_TRACKING_DELAY = 3000000; namespace ovr { controller::Pose toControllerPose(ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose) { // When the sensor-to-world rotation is identity the coordinate axes look like this: // // user // forward // -z // | // y| user // y o----x right // o-----x user // | up // | // z // // Rift // From ABOVE the hand canonical axes looks like this: // // | | | | y | | | | // | | | | | | | | | // | | | | | // |left | / x---- + \ |right| // | _/ z \_ | // | | | | // | | | | // // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down // the rotation to align the Touch axes with those of the hands is: // // touchToHand = halfTurnAboutY * quaterTurnAboutX // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand. // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that // the combination (measurement * offset) is identity at this orientation. // // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) // // An approximate offset for the Touch can be obtained by inspection: // // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis)) // // So the full equation is: // // Q = combinedMeasurement * touchToHand // // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) // // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); static const glm::quat touchToHand = yFlip * quarterX; static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand; static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand; static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f); static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; auto translationOffset = (hand == VRAPI_HAND_LEFT ? leftTranslationOffset : rightTranslationOffset); auto rotationOffset = (hand == VRAPI_HAND_LEFT ? leftRotationOffset : rightRotationOffset); glm::quat rotation = toGlm(handPose.Pose.Orientation); controller::Pose pose; pose.translation = toGlm(handPose.Pose.Position); pose.translation += rotation * translationOffset; pose.rotation = rotation * rotationOffset; pose.angularVelocity = rotation * toGlm(handPose.AngularVelocity); pose.velocity = toGlm(handPose.LinearVelocity); pose.valid = true; return pose; } controller::Pose toControllerPose(ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose, const ovrRigidBodyPosef& lastHandPose) { static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); static const glm::quat touchToHand = yFlip * quarterX; static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand; static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand; static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f); static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; auto translationOffset = (hand == VRAPI_HAND_LEFT ? leftTranslationOffset : rightTranslationOffset); auto rotationOffset = (hand == VRAPI_HAND_LEFT ? leftRotationOffset : rightRotationOffset); glm::quat rotation = toGlm(handPose.Pose.Orientation); controller::Pose pose; pose.translation = toGlm(lastHandPose.Pose.Position); pose.translation += rotation * translationOffset; pose.rotation = rotation * rotationOffset; pose.angularVelocity = toGlm(lastHandPose.AngularVelocity); pose.velocity = toGlm(lastHandPose.LinearVelocity); pose.valid = true; return pose; } } class OculusMobileInputDevice : public controller::InputDevice { friend class OculusMobileControllerManager;public: using Pointer = std::shared_ptr<OculusMobileInputDevice>; static Pointer check(ovrMobile* session); OculusMobileInputDevice(ovrMobile* session, const std::vector<ovrInputTrackedRemoteCapabilities>& devicesCaps); void updateHands(ovrMobile* session); controller::Input::NamedVector getAvailableInputs() const override; QString getDefaultMappingConfig() const override; void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void focusOutEvent() override; bool triggerHapticPulse(float strength, float duration, uint16_t index) override; private: void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose); void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose); void handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const ovrRigidBodyPosef& headPose); void reconnectTouchControllers(ovrMobile* session); // perform an action when the TouchDevice mutex is acquired. using Locker = std::unique_lock<std::recursive_mutex>; template <typename F> void withLock(F&& f) { Locker locker(_lock); f(); } mutable std::recursive_mutex _lock; ovrTracking2 _headTracking; struct HandData { HandData() { state.Header.ControllerType =ovrControllerType_TrackedRemote; } float hapticDuration { 0.0f }; float hapticStrength { 0.0f }; bool valid{ false }; bool lostTracking{ false }; quint64 regainTrackingDeadline; ovrRigidBodyPosef lastPose; ovrInputTrackedRemoteCapabilities caps; ovrInputStateTrackedRemote state; ovrResult stateResult{ ovrError_NotInitialized }; ovrTracking tracking; ovrResult trackingResult{ ovrError_NotInitialized }; bool setHapticFeedback(float strength, float duration) {#if 0 bool success = true; bool sessionSuccess = ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ if (strength == 0.0f) { hapticStrength = 0.0f; hapticDuration = 0.0f; } else { hapticStrength = (duration > hapticDuration) ? strength : hapticStrength; if (vrapi_SetHapticVibrationSimple(session, caps.Header.DeviceID, hapticStrength) != ovrSuccess) { success = false; } hapticDuration = std::max(duration, hapticDuration); } }); return success && sessionSuccess;#else return true;#endif } void stopHapticPulse() { ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ vrapi_SetHapticVibrationSimple(session, caps.Header.DeviceID, 0.0f); }); } bool isValid() const { return (stateResult == ovrSuccess) && (trackingResult == ovrSuccess); } void update(ovrMobile* session, double time = 0.0) { const auto& deviceId = caps.Header.DeviceID; stateResult = vrapi_GetCurrentInputState(session, deviceId, &state.Header); trackingResult = vrapi_GetInputTrackingState(session, deviceId, 0.0, &tracking); } }; std::array<HandData, 2> _hands;}; OculusMobileInputDevice::Pointer OculusMobileInputDevice::check(ovrMobile *session) { Pointer result; std::vector<ovrInputTrackedRemoteCapabilities> devicesCaps; { uint32_t deviceIndex { 0 }; ovrInputCapabilityHeader capsHeader; while (vrapi_EnumerateInputDevices(session, deviceIndex, &capsHeader) >= 0) { if (capsHeader.Type == ovrControllerType_TrackedRemote) { ovrInputTrackedRemoteCapabilities caps; caps.Header = capsHeader; vrapi_GetInputDeviceCapabilities(session, &caps.Header); devicesCaps.push_back(caps); } ++deviceIndex; } } if (!devicesCaps.empty()) { result.reset(new OculusMobileInputDevice(session, devicesCaps)); } return result;} static OculusMobileInputDevice::Pointer oculusMobileControllers; bool OculusMobileControllerManager::isHandController() const { return oculusMobileControllers.operator bool();} bool OculusMobileControllerManager::isSupported() const { return true;} bool OculusMobileControllerManager::activate() { InputPlugin::activate(); checkForConnectedDevices(); return true;} void OculusMobileControllerManager::checkForConnectedDevices() { if (oculusMobileControllers) { return; } ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ oculusMobileControllers = OculusMobileInputDevice::check(session); if (oculusMobileControllers) { auto userInputMapper = DependencyManager::get<controller::UserInputMapper>(); userInputMapper->registerDevice(oculusMobileControllers); } });} void OculusMobileControllerManager::deactivate() { InputPlugin::deactivate(); // unregister with UserInputMapper auto userInputMapper = DependencyManager::get<controller::UserInputMapper>(); if (oculusMobileControllers) { userInputMapper->removeDevice(oculusMobileControllers->getDeviceID()); oculusMobileControllers.reset(); }} void OculusMobileControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { PerformanceTimer perfTimer("OculusMobileInputDevice::update"); checkForConnectedDevices(); if (!oculusMobileControllers) { return; } bool updated = ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ oculusMobileControllers->updateHands(session); }); if (updated) { oculusMobileControllers->update(deltaTime, inputCalibrationData); }} void OculusMobileControllerManager::pluginFocusOutEvent() { if (oculusMobileControllers) { oculusMobileControllers->focusOutEvent(); }} QStringList OculusMobileControllerManager::getSubdeviceNames() { QStringList devices; if (oculusMobileControllers) { devices << oculusMobileControllers->getName(); } return devices;} using namespace controller; static const std::vector<std::pair<ovrButton, StandardButtonChannel>> BUTTON_MAP { { { ovrButton_Up, DU }, { ovrButton_Down, DD }, { ovrButton_Left, DL }, { ovrButton_Right, DR }, { ovrButton_Enter, START }, { ovrButton_Back, BACK }, { ovrButton_X, X }, { ovrButton_Y, Y }, { ovrButton_A, A }, { ovrButton_B, B }, { ovrButton_LThumb, LS }, { ovrButton_RThumb, RS }, //{ ovrButton_LShoulder, LB }, //{ ovrButton_RShoulder, RB },} }; static const std::vector<std::pair<ovrTouch, StandardButtonChannel>> LEFT_TOUCH_MAP { { { ovrTouch_X, LEFT_PRIMARY_THUMB_TOUCH }, { ovrTouch_Y, LEFT_SECONDARY_THUMB_TOUCH }, { ovrTouch_LThumb, LS_TOUCH }, { ovrTouch_ThumbUp, LEFT_THUMB_UP }, { ovrTouch_IndexTrigger, LEFT_PRIMARY_INDEX_TOUCH }, { ovrTouch_IndexPointing, LEFT_INDEX_POINT },} }; static const std::vector<std::pair<ovrTouch, StandardButtonChannel>> RIGHT_TOUCH_MAP { { { ovrTouch_A, RIGHT_PRIMARY_THUMB_TOUCH }, { ovrTouch_B, RIGHT_SECONDARY_THUMB_TOUCH }, { ovrTouch_RThumb, RS_TOUCH }, { ovrTouch_ThumbUp, RIGHT_THUMB_UP }, { ovrTouch_IndexTrigger, RIGHT_PRIMARY_INDEX_TOUCH }, { ovrTouch_IndexPointing, RIGHT_INDEX_POINT },} }; void OculusMobileInputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _buttonPressedMap.clear(); int numTrackedControllers = 0; quint64 currentTime = usecTimestampNow(); handleHeadPose(deltaTime, inputCalibrationData, _headTracking.HeadPose); static const auto REQUIRED_HAND_STATUS = VRAPI_TRACKING_STATUS_ORIENTATION_TRACKED | VRAPI_TRACKING_STATUS_POSITION_TRACKED; ovr::for_each_hand([&](ovrTrackedDeviceTypeId hand) { size_t handIndex = (hand == VRAPI_TRACKED_DEVICE_HAND_LEFT) ? 0 : 1; int controller = (hand == VRAPI_TRACKED_DEVICE_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND; auto& handData = _hands[handIndex]; const auto& tracking = handData.tracking; ++numTrackedControllers; // Disable hand tracking while in Oculus Dash (Dash renders it's own hands)// if (!hasInputFocus) {// _poseStateMap.erase(controller);// _poseStateMap[controller].valid = false;// return;// } if (REQUIRED_HAND_STATUS == (tracking.Status & REQUIRED_HAND_STATUS)) { _poseStateMap.erase(controller); handlePose(deltaTime, inputCalibrationData, hand, tracking.HeadPose); handData.lostTracking = false; handData.lastPose = tracking.HeadPose; return; } if (handData.lostTracking) { if (currentTime > handData.regainTrackingDeadline) { _poseStateMap.erase(controller); _poseStateMap[controller].valid = false; return; } } else { quint64 deadlineToRegainTracking = currentTime + LOST_TRACKING_DELAY; handData.regainTrackingDeadline = deadlineToRegainTracking; handData.lostTracking = true; } handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HeadPose); }); using namespace controller; // Axes { const auto& inputState = _hands[0].state; _axisStateMap[LX].value = inputState.JoystickNoDeadZone.x; _axisStateMap[LY].value = inputState.JoystickNoDeadZone.y; _axisStateMap[LT].value = inputState.IndexTrigger; _axisStateMap[LEFT_GRIP].value = inputState.GripTrigger; for (const auto& pair : BUTTON_MAP) { if (inputState.Buttons & pair.first) { _buttonPressedMap.insert(pair.second); qDebug()<<"AAAA:BUTTON PRESSED "<<pair.second; } } for (const auto& pair : LEFT_TOUCH_MAP) { if (inputState.Touches & pair.first) { _buttonPressedMap.insert(pair.second); } } } { const auto& inputState = _hands[1].state; _axisStateMap[RX].value = inputState.JoystickNoDeadZone.x; _axisStateMap[RY].value = inputState.JoystickNoDeadZone.y; _axisStateMap[RT].value = inputState.IndexTrigger; _axisStateMap[RIGHT_GRIP].value = inputState.GripTrigger; for (const auto& pair : BUTTON_MAP) { if (inputState.Buttons & pair.first) { _buttonPressedMap.insert(pair.second); } } for (const auto& pair : RIGHT_TOUCH_MAP) { if (inputState.Touches & pair.first) { _buttonPressedMap.insert(pair.second); } } } // Haptics { Locker locker(_lock); for (auto& hand : _hands) { if (hand.hapticDuration) { hand.hapticDuration -= deltaTime * 1000.0f; // milliseconds } else { hand.stopHapticPulse(); } } }} void OculusMobileInputDevice::focusOutEvent() { _axisStateMap.clear(); _buttonPressedMap.clear();}; void OculusMobileInputDevice::handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose) { auto poseId = (hand == VRAPI_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND; auto& pose = _poseStateMap[poseId]; pose = ovr::toControllerPose(hand, handPose); // transform into avatar frame glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; pose = pose.transform(controllerToAvatar);} void OculusMobileInputDevice::handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const ovrRigidBodyPosef& headPose) { glm::mat4 mat = createMatFromQuatAndPos(ovr::toGlm(headPose.Pose.Orientation), ovr::toGlm(headPose.Pose.Position)); //perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z glm::mat4 matYFlip = mat * Matrices::Y_180; controller::Pose pose(extractTranslation(matYFlip), glmExtractRotation(matYFlip), ovr::toGlm(headPose.LinearVelocity), // XXX * matYFlip ? ovr::toGlm(headPose.AngularVelocity)); Lines 1–500 of 731. Load more
// // Created by Bradley Austin Davis on 2018/11/15 // Copyright 2013-2018 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OculusMobileControllerManager.h" #include <array> #include <ui-plugins/PluginContainer.h> #include <controllers/UserInputMapper.h> #include <controllers/StandardControls.h> #include <input-plugins/KeyboardMouseDevice.h> #include <PerfStat.h> #include <PathUtils.h> #include <NumericalConstants.h> #include <StreamUtils.h> #include <VrApi.h> #include <VrApi_Input.h> #include <ovr/Helpers.h> #include "Logging.h" #include <ovr/VrHandler.h> const char* OculusMobileControllerManager::NAME = "Oculus"; const quint64 LOST_TRACKING_DELAY = 3000000; namespace ovr { controller::Pose toControllerPose(ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose) { // When the sensor-to-world rotation is identity the coordinate axes look like this: // // user // forward // -z // | // y| user // y o----x right // o-----x user // | up // | // z // // Rift // From ABOVE the hand canonical axes looks like this: // // | | | | y | | | | // | | | | | | | | | // | | | | | // |left | / x---- + \ |right| // | _/ z \_ | // | | | | // | | | | // // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down // the rotation to align the Touch axes with those of the hands is: // // touchToHand = halfTurnAboutY * quaterTurnAboutX // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand. // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that // the combination (measurement * offset) is identity at this orientation. // // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) // // An approximate offset for the Touch can be obtained by inspection: // // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis)) // // So the full equation is: // // Q = combinedMeasurement * touchToHand // // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) // // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); static const glm::quat touchToHand = yFlip * quarterX; static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand; static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand; static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f); static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; auto translationOffset = (hand == VRAPI_HAND_LEFT ? leftTranslationOffset : rightTranslationOffset); auto rotationOffset = (hand == VRAPI_HAND_LEFT ? leftRotationOffset : rightRotationOffset); glm::quat rotation = toGlm(handPose.Pose.Orientation); controller::Pose pose; pose.translation = toGlm(handPose.Pose.Position); pose.translation += rotation * translationOffset; pose.rotation = rotation * rotationOffset; pose.angularVelocity = rotation * toGlm(handPose.AngularVelocity); pose.velocity = toGlm(handPose.LinearVelocity); pose.valid = true; return pose; } controller::Pose toControllerPose(ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose, const ovrRigidBodyPosef& lastHandPose) { static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); static const glm::quat touchToHand = yFlip * quarterX; static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand; static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand; static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f); static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; auto translationOffset = (hand == VRAPI_HAND_LEFT ? leftTranslationOffset : rightTranslationOffset); auto rotationOffset = (hand == VRAPI_HAND_LEFT ? leftRotationOffset : rightRotationOffset); glm::quat rotation = toGlm(handPose.Pose.Orientation); controller::Pose pose; pose.translation = toGlm(lastHandPose.Pose.Position); pose.translation += rotation * translationOffset; pose.rotation = rotation * rotationOffset; pose.angularVelocity = toGlm(lastHandPose.AngularVelocity); pose.velocity = toGlm(lastHandPose.LinearVelocity); pose.valid = true; return pose; } } class OculusMobileInputDevice : public controller::InputDevice { friend class OculusMobileControllerManager; public: using Pointer = std::shared_ptr<OculusMobileInputDevice>; static Pointer check(ovrMobile* session); OculusMobileInputDevice(ovrMobile* session, const std::vector<ovrInputTrackedRemoteCapabilities>& devicesCaps); void updateHands(ovrMobile* session); controller::Input::NamedVector getAvailableInputs() const override; QString getDefaultMappingConfig() const override; void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void focusOutEvent() override; bool triggerHapticPulse(float strength, float duration, uint16_t index) override; private: void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose); void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose); void handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const ovrRigidBodyPosef& headPose); void reconnectTouchControllers(ovrMobile* session); // perform an action when the TouchDevice mutex is acquired. using Locker = std::unique_lock<std::recursive_mutex>; template <typename F> void withLock(F&& f) { Locker locker(_lock); f(); } mutable std::recursive_mutex _lock; ovrTracking2 _headTracking; struct HandData { HandData() { state.Header.ControllerType =ovrControllerType_TrackedRemote; } float hapticDuration { 0.0f }; float hapticStrength { 0.0f }; bool valid{ false }; bool lostTracking{ false }; quint64 regainTrackingDeadline; ovrRigidBodyPosef lastPose; ovrInputTrackedRemoteCapabilities caps; ovrInputStateTrackedRemote state; ovrResult stateResult{ ovrError_NotInitialized }; ovrTracking tracking; ovrResult trackingResult{ ovrError_NotInitialized }; bool setHapticFeedback(float strength, float duration) { #if 0 bool success = true; bool sessionSuccess = ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ if (strength == 0.0f) { hapticStrength = 0.0f; hapticDuration = 0.0f; } else { hapticStrength = (duration > hapticDuration) ? strength : hapticStrength; if (vrapi_SetHapticVibrationSimple(session, caps.Header.DeviceID, hapticStrength) != ovrSuccess) { success = false; } hapticDuration = std::max(duration, hapticDuration); } }); return success && sessionSuccess; #else return true; #endif } void stopHapticPulse() { ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ vrapi_SetHapticVibrationSimple(session, caps.Header.DeviceID, 0.0f); }); } bool isValid() const { return (stateResult == ovrSuccess) && (trackingResult == ovrSuccess); } void update(ovrMobile* session, double time = 0.0) { const auto& deviceId = caps.Header.DeviceID; stateResult = vrapi_GetCurrentInputState(session, deviceId, &state.Header); trackingResult = vrapi_GetInputTrackingState(session, deviceId, 0.0, &tracking); } }; std::array<HandData, 2> _hands; }; OculusMobileInputDevice::Pointer OculusMobileInputDevice::check(ovrMobile *session) { Pointer result; std::vector<ovrInputTrackedRemoteCapabilities> devicesCaps; { uint32_t deviceIndex { 0 }; ovrInputCapabilityHeader capsHeader; while (vrapi_EnumerateInputDevices(session, deviceIndex, &capsHeader) >= 0) { if (capsHeader.Type == ovrControllerType_TrackedRemote) { ovrInputTrackedRemoteCapabilities caps; caps.Header = capsHeader; vrapi_GetInputDeviceCapabilities(session, &caps.Header); devicesCaps.push_back(caps); } ++deviceIndex; } } if (!devicesCaps.empty()) { result.reset(new OculusMobileInputDevice(session, devicesCaps)); } return result; } static OculusMobileInputDevice::Pointer oculusMobileControllers; bool OculusMobileControllerManager::isHandController() const { return oculusMobileControllers.operator bool(); } bool OculusMobileControllerManager::isSupported() const { return true; } bool OculusMobileControllerManager::activate() { InputPlugin::activate(); checkForConnectedDevices(); return true; } void OculusMobileControllerManager::checkForConnectedDevices() { if (oculusMobileControllers) { return; } ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ oculusMobileControllers = OculusMobileInputDevice::check(session); if (oculusMobileControllers) { auto userInputMapper = DependencyManager::get<controller::UserInputMapper>(); userInputMapper->registerDevice(oculusMobileControllers); } }); } void OculusMobileControllerManager::deactivate() { InputPlugin::deactivate(); // unregister with UserInputMapper auto userInputMapper = DependencyManager::get<controller::UserInputMapper>(); if (oculusMobileControllers) { userInputMapper->removeDevice(oculusMobileControllers->getDeviceID()); oculusMobileControllers.reset(); } } void OculusMobileControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { PerformanceTimer perfTimer("OculusMobileInputDevice::update"); checkForConnectedDevices(); if (!oculusMobileControllers) { return; } bool updated = ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ oculusMobileControllers->updateHands(session); }); if (updated) { oculusMobileControllers->update(deltaTime, inputCalibrationData); } } void OculusMobileControllerManager::pluginFocusOutEvent() { if (oculusMobileControllers) { oculusMobileControllers->focusOutEvent(); } } QStringList OculusMobileControllerManager::getSubdeviceNames() { QStringList devices; if (oculusMobileControllers) { devices << oculusMobileControllers->getName(); } return devices; } using namespace controller; static const std::vector<std::pair<ovrButton, StandardButtonChannel>> BUTTON_MAP { { { ovrButton_Up, DU }, { ovrButton_Down, DD }, { ovrButton_Left, DL }, { ovrButton_Right, DR }, { ovrButton_Enter, START }, { ovrButton_Back, BACK }, { ovrButton_X, X }, { ovrButton_Y, Y }, { ovrButton_A, A }, { ovrButton_B, B }, { ovrButton_LThumb, LS }, { ovrButton_RThumb, RS }, //{ ovrButton_LShoulder, LB }, //{ ovrButton_RShoulder, RB }, } }; static const std::vector<std::pair<ovrTouch, StandardButtonChannel>> LEFT_TOUCH_MAP { { { ovrTouch_X, LEFT_PRIMARY_THUMB_TOUCH }, { ovrTouch_Y, LEFT_SECONDARY_THUMB_TOUCH }, { ovrTouch_LThumb, LS_TOUCH }, { ovrTouch_ThumbUp, LEFT_THUMB_UP }, { ovrTouch_IndexTrigger, LEFT_PRIMARY_INDEX_TOUCH }, { ovrTouch_IndexPointing, LEFT_INDEX_POINT }, } }; static const std::vector<std::pair<ovrTouch, StandardButtonChannel>> RIGHT_TOUCH_MAP { { { ovrTouch_A, RIGHT_PRIMARY_THUMB_TOUCH }, { ovrTouch_B, RIGHT_SECONDARY_THUMB_TOUCH }, { ovrTouch_RThumb, RS_TOUCH }, { ovrTouch_ThumbUp, RIGHT_THUMB_UP }, { ovrTouch_IndexTrigger, RIGHT_PRIMARY_INDEX_TOUCH }, { ovrTouch_IndexPointing, RIGHT_INDEX_POINT }, } }; void OculusMobileInputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _buttonPressedMap.clear(); int numTrackedControllers = 0; quint64 currentTime = usecTimestampNow(); handleHeadPose(deltaTime, inputCalibrationData, _headTracking.HeadPose); static const auto REQUIRED_HAND_STATUS = VRAPI_TRACKING_STATUS_ORIENTATION_TRACKED | VRAPI_TRACKING_STATUS_POSITION_TRACKED; ovr::for_each_hand([&](ovrTrackedDeviceTypeId hand) { size_t handIndex = (hand == VRAPI_TRACKED_DEVICE_HAND_LEFT) ? 0 : 1; int controller = (hand == VRAPI_TRACKED_DEVICE_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND; auto& handData = _hands[handIndex]; const auto& tracking = handData.tracking; ++numTrackedControllers; // Disable hand tracking while in Oculus Dash (Dash renders it's own hands) // if (!hasInputFocus) { // _poseStateMap.erase(controller); // _poseStateMap[controller].valid = false; // return; // } if (REQUIRED_HAND_STATUS == (tracking.Status & REQUIRED_HAND_STATUS)) { _poseStateMap.erase(controller); handlePose(deltaTime, inputCalibrationData, hand, tracking.HeadPose); handData.lostTracking = false; handData.lastPose = tracking.HeadPose; return; } if (handData.lostTracking) { if (currentTime > handData.regainTrackingDeadline) { _poseStateMap.erase(controller); _poseStateMap[controller].valid = false; return; } } else { quint64 deadlineToRegainTracking = currentTime + LOST_TRACKING_DELAY; handData.regainTrackingDeadline = deadlineToRegainTracking; handData.lostTracking = true; } handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HeadPose); }); using namespace controller; // Axes { const auto& inputState = _hands[0].state; _axisStateMap[LX].value = inputState.JoystickNoDeadZone.x; _axisStateMap[LY].value = inputState.JoystickNoDeadZone.y; _axisStateMap[LT].value = inputState.IndexTrigger; _axisStateMap[LEFT_GRIP].value = inputState.GripTrigger; for (const auto& pair : BUTTON_MAP) { if (inputState.Buttons & pair.first) { _buttonPressedMap.insert(pair.second); qDebug()<<"AAAA:BUTTON PRESSED "<<pair.second; } } for (const auto& pair : LEFT_TOUCH_MAP) { if (inputState.Touches & pair.first) { _buttonPressedMap.insert(pair.second); } } } { const auto& inputState = _hands[1].state; _axisStateMap[RX].value = inputState.JoystickNoDeadZone.x; _axisStateMap[RY].value = inputState.JoystickNoDeadZone.y; _axisStateMap[RT].value = inputState.IndexTrigger; _axisStateMap[RIGHT_GRIP].value = inputState.GripTrigger; for (const auto& pair : BUTTON_MAP) { if (inputState.Buttons & pair.first) { _buttonPressedMap.insert(pair.second); } } for (const auto& pair : RIGHT_TOUCH_MAP) { if (inputState.Touches & pair.first) { _buttonPressedMap.insert(pair.second); } } } // Haptics { Locker locker(_lock); for (auto& hand : _hands) { if (hand.hapticDuration) { hand.hapticDuration -= deltaTime * 1000.0f; // milliseconds } else { hand.stopHapticPulse(); } } } } void OculusMobileInputDevice::focusOutEvent() { _axisStateMap.clear(); _buttonPressedMap.clear(); }; void OculusMobileInputDevice::handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose) { auto poseId = (hand == VRAPI_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND; auto& pose = _poseStateMap[poseId]; pose = ovr::toControllerPose(hand, handPose); // transform into avatar frame glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; pose = pose.transform(controllerToAvatar); } void OculusMobileInputDevice::handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const ovrRigidBodyPosef& headPose) { glm::mat4 mat = createMatFromQuatAndPos(ovr::toGlm(headPose.Pose.Orientation), ovr::toGlm(headPose.Pose.Position)); //perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z glm::mat4 matYFlip = mat * Matrices::Y_180; controller::Pose pose(extractTranslation(matYFlip), glmExtractRotation(matYFlip), ovr::toGlm(headPose.LinearVelocity), // XXX * matYFlip ? ovr::toGlm(headPose.AngularVelocity));