// // OculusControllerManager.cpp // input-plugins/src/input-plugins // // Created by Bradley Austin Davis 2016/03/04. // Copyright 2013-2016 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 "OculusControllerManager.h" #include #include #include #include #include #include #include #include #include #include #include "OculusHelpers.h" using namespace hifi; const char* OculusControllerManager::NAME = "Oculus"; const QString OCULUS_LAYOUT = "OculusConfiguration.qml"; const quint64 LOST_TRACKING_DELAY = 3000000; bool OculusControllerManager::isSupported() const { return hifi::ovr::available(); } bool OculusControllerManager::activate() { InputPlugin::activate(); checkForConnectedDevices(); return true; } QString OculusControllerManager::configurationLayout() { return OCULUS_LAYOUT; } void OculusControllerManager::setConfigurationSettings(const QJsonObject configurationSettings) { if (configurationSettings.contains("trackControllersInOculusHome")) { _trackControllersInOculusHome.set(configurationSettings["trackControllersInOculusHome"].toBool()); } } QJsonObject OculusControllerManager::configurationSettings() { QJsonObject configurationSettings; configurationSettings["trackControllersInOculusHome"] = _trackControllersInOculusHome.get(); return configurationSettings; } void OculusControllerManager::checkForConnectedDevices() { if (_touch && _remote) { return; } ovr::withSession([&] (ovrSession session) { unsigned int controllerConnected = ovr_GetConnectedControllerTypes(session); if (!_remote && (controllerConnected & ovrControllerType_Remote) == ovrControllerType_Remote) { if (OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Remote, &_remoteInputState))) { auto userInputMapper = DependencyManager::get(); _remote = std::make_shared(*this); userInputMapper->registerDevice(_remote); } } if (!_touch && (controllerConnected & ovrControllerType_Touch) != 0) { if (OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Touch, &_touchInputState))) { auto userInputMapper = DependencyManager::get(); _touch = std::make_shared(*this); userInputMapper->registerDevice(_touch); } } }); } void OculusControllerManager::deactivate() { InputPlugin::deactivate(); // unregister with UserInputMapper auto userInputMapper = DependencyManager::get(); if (_touch) { userInputMapper->removeDevice(_touch->getDeviceID()); } if (_remote) { userInputMapper->removeDevice(_remote->getDeviceID()); } } void OculusControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { PerformanceTimer perfTimer("OculusControllerManager::TouchDevice::update"); checkForConnectedDevices(); bool updateRemote = false, updateTouch = false; ovr::withSession([&](ovrSession session) { if (_touch) { updateTouch = OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Touch, &_touchInputState)); if (!updateTouch) { qCWarning(oculusLog) << "Unable to read Oculus touch input state" << ovr::getError(); } } if (_remote) { updateRemote = OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Remote, &_remoteInputState)); if (!updateRemote) { qCWarning(oculusLog) << "Unable to read Oculus remote input state" << ovr::getError(); } } }); if (_touch && updateTouch) { _touch->update(deltaTime, inputCalibrationData); } if (_remote && updateRemote) { _remote->update(deltaTime, inputCalibrationData); } } void OculusControllerManager::pluginFocusOutEvent() { if (_touch) { _touch->focusOutEvent(); } if (_remote) { _remote->focusOutEvent(); } } void OculusControllerManager::stopHapticPulse(bool leftHand) { if (_touch) { _touch->stopHapticPulse(leftHand); } } QStringList OculusControllerManager::getSubdeviceNames() { QStringList devices; if (_touch) { devices << _touch->getName(); } if (_remote) { devices << _remote->getName(); } return devices; } using namespace controller; static const std::vector> 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> TOUCH_MAP { { { ovrTouch_X, LEFT_PRIMARY_THUMB_TOUCH }, { ovrTouch_Y, LEFT_SECONDARY_THUMB_TOUCH }, { ovrTouch_A, RIGHT_PRIMARY_THUMB_TOUCH }, { ovrTouch_B, RIGHT_SECONDARY_THUMB_TOUCH }, { ovrTouch_LIndexTrigger, LEFT_PRIMARY_INDEX_TOUCH }, { ovrTouch_RIndexTrigger, RIGHT_PRIMARY_INDEX_TOUCH }, { ovrTouch_LThumb, LS_TOUCH }, { ovrTouch_RThumb, RS_TOUCH }, { ovrTouch_LThumbUp, LEFT_THUMB_UP }, { ovrTouch_RThumbUp, RIGHT_THUMB_UP }, { ovrTouch_LIndexPointing, LEFT_INDEX_POINT }, { ovrTouch_RIndexPointing, RIGHT_INDEX_POINT }, } }; controller::Input::NamedVector OculusControllerManager::RemoteDevice::getAvailableInputs() const { using namespace controller; QVector availableInputs { makePair(DU, "DU"), makePair(DD, "DD"), makePair(DL, "DL"), makePair(DR, "DR"), makePair(START, "Start"), makePair(BACK, "Back"), }; return availableInputs; } QString OculusControllerManager::RemoteDevice::getDefaultMappingConfig() const { static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/oculus_remote.json"; return MAPPING_JSON; } void OculusControllerManager::RemoteDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _buttonPressedMap.clear(); const auto& inputState = _parent._remoteInputState; for (const auto& pair : BUTTON_MAP) { if (inputState.Buttons & pair.first) { _buttonPressedMap.insert(pair.second); } } } void OculusControllerManager::RemoteDevice::focusOutEvent() { _buttonPressedMap.clear(); } void OculusControllerManager::TouchDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _buttonPressedMap.clear(); int numTrackedControllers = 0; quint64 currentTime = usecTimestampNow(); static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked | ovrStatus_PositionTracked; bool hasInputFocus = ovr::hasInputFocus(); bool trackControllersInOculusHome = _parent._trackControllersInOculusHome.get(); auto tracking = ovr::getTrackingState(); // ovr_GetTrackingState(_parent._session, 0, false); ovr::for_each_hand([&](ovrHandType hand) { ++numTrackedControllers; int controller = (hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND); // Disable hand tracking while in Oculus Dash (Dash renders it's own hands) if (!hasInputFocus && !trackControllersInOculusHome) { _poseStateMap.erase(controller); _poseStateMap[controller].valid = false; return; } if (REQUIRED_HAND_STATUS == (tracking.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { _poseStateMap.erase(controller); handlePose(deltaTime, inputCalibrationData, hand, tracking.HandPoses[hand]); _lostTracking[controller] = false; _lastControllerPose[controller] = tracking.HandPoses[hand]; return; } if (_lostTracking[controller]) { if (currentTime > _regainTrackingDeadline[controller]) { _poseStateMap.erase(controller); _poseStateMap[controller].valid = false; return; } } else { quint64 deadlineToRegainTracking = currentTime + LOST_TRACKING_DELAY; _regainTrackingDeadline[controller] = deadlineToRegainTracking; _lostTracking[controller] = true; } handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HandPoses[hand]); }); if (ovr::hmdMounted()) { handleHeadPose(deltaTime, inputCalibrationData, tracking.HeadPose); } else { _poseStateMap[controller::HEAD].valid = false; } using namespace controller; // Axes const auto& inputState = _parent._touchInputState; _axisStateMap[LX].value = inputState.Thumbstick[ovrHand_Left].x; _axisStateMap[LY].value = inputState.Thumbstick[ovrHand_Left].y; _axisStateMap[LT].value = inputState.IndexTrigger[ovrHand_Left]; _axisStateMap[LEFT_GRIP].value = inputState.HandTrigger[ovrHand_Left]; _axisStateMap[RX].value = inputState.Thumbstick[ovrHand_Right].x; _axisStateMap[RY].value = inputState.Thumbstick[ovrHand_Right].y; _axisStateMap[RT].value = inputState.IndexTrigger[ovrHand_Right]; _axisStateMap[RIGHT_GRIP].value = inputState.HandTrigger[ovrHand_Right]; // Buttons for (const auto& pair : BUTTON_MAP) { if (inputState.Buttons & pair.first) { _buttonPressedMap.insert(pair.second); } } // Touches for (const auto& pair : TOUCH_MAP) { if (inputState.Touches & pair.first) { _buttonPressedMap.insert(pair.second); } } // Haptics { Locker locker(_lock); if (_leftHapticDuration > 0.0f) { _leftHapticDuration -= deltaTime * 1000.0f; // milliseconds } else { stopHapticPulse(true); } if (_rightHapticDuration > 0.0f) { _rightHapticDuration -= deltaTime * 1000.0f; // milliseconds } else { stopHapticPulse(false); } } } void OculusControllerManager::TouchDevice::focusOutEvent() { _axisStateMap.clear(); _buttonPressedMap.clear(); }; void OculusControllerManager::TouchDevice::handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose) { auto poseId = hand == ovrHand_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 OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const ovrPoseStatef& headPose) { glm::mat4 mat = createMatFromQuatAndPos(ovr::toGlm(headPose.ThePose.Orientation), ovr::toGlm(headPose.ThePose.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)); glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; glm::mat4 defaultHeadOffset; if (inputCalibrationData.hmdAvatarAlignmentType == controller::HmdAvatarAlignmentType::Eyes) { // align the eyes of the user with the eyes of the avatar defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat; } else { // align the head of the user with the head of the avatar defaultHeadOffset = createMatFromQuatAndPos(Quaternions::IDENTITY, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); } pose.valid = true; _poseStateMap[controller::HEAD] = pose.postTransform(defaultHeadOffset).transform(sensorToAvatar); } void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose) { auto poseId = (hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND); auto& pose = _poseStateMap[poseId]; auto lastHandPose = _lastControllerPose[poseId]; pose = ovr::toControllerPose(hand, handPose, lastHandPose); glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; pose = pose.transform(controllerToAvatar); } bool OculusControllerManager::TouchDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) { Locker locker(_lock); bool toReturn = true; ovr::withSession([&](ovrSession session) { if (hand == controller::BOTH || hand == controller::LEFT) { if (strength == 0.0f) { _leftHapticStrength = 0.0f; _leftHapticDuration = 0.0f; } else { _leftHapticStrength = (duration > _leftHapticDuration) ? strength : _leftHapticStrength; if (ovr_SetControllerVibration(session, ovrControllerType_LTouch, 1.0f, _leftHapticStrength) != ovrSuccess) { toReturn = false; } _leftHapticDuration = std::max(duration, _leftHapticDuration); } } if (hand == controller::BOTH || hand == controller::RIGHT) { if (strength == 0.0f) { _rightHapticStrength = 0.0f; _rightHapticDuration = 0.0f; } else { _rightHapticStrength = (duration > _rightHapticDuration) ? strength : _rightHapticStrength; if (ovr_SetControllerVibration(session, ovrControllerType_RTouch, 1.0f, _rightHapticStrength) != ovrSuccess) { toReturn = false; } _rightHapticDuration = std::max(duration, _rightHapticDuration); } } }); return toReturn; } void OculusControllerManager::TouchDevice::stopHapticPulse(bool leftHand) { auto handType = (leftHand ? ovrControllerType_LTouch : ovrControllerType_RTouch); ovr::withSession([&](ovrSession session) { ovr_SetControllerVibration(session, handType, 0.0f, 0.0f); }); } /**jsdoc *

The Controller.Hardware.OculusTouch object has properties representing the Oculus Rift. The property values * are integer IDs, uniquely identifying each output. Read-only.

*

These outputs can be mapped to actions or functions or Controller.Standard items in a {@link RouteObject} * mapping.

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
PropertyTypeDataDescription
Buttons
Anumbernumber"A" button pressed.
Bnumbernumber"B" button pressed.
Xnumbernumber"X" button pressed.
Ynumbernumber"Y" button pressed.
LeftApplicationMenunumbernumberLeft application menu button pressed. *
RightApplicationMenunumbernumberRight application menu button pressed. *
Sticks
LXnumbernumberLeft stick x-axis scale.
LYnumbernumberLeft stick y-axis scale.
RXnumbernumberRight stick x-axis scale.
RYnumbernumberRight stick y-axis scale.
LSnumbernumberLeft stick button pressed.
RSnumbernumberRight stick button pressed.
LSTouchnumbernumberLeft stick is touched.
RSTouchnumbernumberRight stick is touched.
Triggers
LTnumbernumberLeft trigger scale.
RTnumbernumberRight trigger scale.
LeftGripnumbernumberLeft grip scale.
RightGripnumbernumberRight grip scale.
Finger Abstractions
LeftPrimaryThumbTouchnumbernumberLeft thumb touching primary thumb * button.
LeftSecondaryThumbTouchnumbernumberLeft thumb touching secondary thumb * button.
LeftThumbUpnumbernumberLeft thumb not touching primary or secondary * thumb buttons.
RightPrimaryThumbTouchnumbernumberRight thumb touching primary thumb * button.
RightSecondaryThumbTouchnumbernumberRight thumb touching secondary thumb * button.
RightThumbUpnumbernumberRight thumb not touching primary or secondary * thumb buttons.
LeftPrimaryIndexTouchnumbernumberLeft index finger is touching primary * index finger control.
LeftIndexPointnumbernumberLeft index finger is pointing, not touching * primary or secondary index finger controls.
RightPrimaryIndexTouchnumbernumberRight index finger is touching primary * index finger control.
RightIndexPointnumbernumberRight index finger is pointing, not touching * primary or secondary index finger controls.
Avatar Skeleton
Headnumber{@link Pose}Head pose.
LeftHandnumber{@link Pose}Left hand pose.
RightHandnumber{@link Pose}right hand pose.
* @typedef {object} Controller.Hardware-OculusTouch */ controller::Input::NamedVector OculusControllerManager::TouchDevice::getAvailableInputs() const { using namespace controller; QVector availableInputs{ // buttons makePair(A, "A"), makePair(B, "B"), makePair(X, "X"), makePair(Y, "Y"), // trackpad analogs makePair(LX, "LX"), makePair(LY, "LY"), makePair(RX, "RX"), makePair(RY, "RY"), // triggers makePair(LT, "LT"), makePair(RT, "RT"), // trigger buttons //makePair(LB, "LB"), //makePair(RB, "RB"), // side grip triggers makePair(LEFT_GRIP, "LeftGrip"), makePair(RIGHT_GRIP, "RightGrip"), // joystick buttons makePair(LS, "LS"), makePair(RS, "RS"), makePair(LEFT_HAND, "LeftHand"), makePair(RIGHT_HAND, "RightHand"), makePair(HEAD, "Head"), makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"), makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"), makePair(RIGHT_PRIMARY_THUMB_TOUCH, "RightPrimaryThumbTouch"), makePair(RIGHT_SECONDARY_THUMB_TOUCH, "RightSecondaryThumbTouch"), makePair(LEFT_PRIMARY_INDEX_TOUCH, "LeftPrimaryIndexTouch"), makePair(RIGHT_PRIMARY_INDEX_TOUCH, "RightPrimaryIndexTouch"), makePair(LS_TOUCH, "LSTouch"), makePair(RS_TOUCH, "RSTouch"), makePair(LEFT_THUMB_UP, "LeftThumbUp"), makePair(RIGHT_THUMB_UP, "RightThumbUp"), makePair(LEFT_INDEX_POINT, "LeftIndexPoint"), makePair(RIGHT_INDEX_POINT, "RightIndexPoint"), makePair(BACK, "LeftApplicationMenu"), makePair(START, "RightApplicationMenu"), }; return availableInputs; } QString OculusControllerManager::TouchDevice::getDefaultMappingConfig() const { static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/oculus_touch.json"; return MAPPING_JSON; }