diff --git a/libraries/controllers/src/controllers/InputDevice.h b/libraries/controllers/src/controllers/InputDevice.h index afb1f7d1f7..10e1a104f4 100644 --- a/libraries/controllers/src/controllers/InputDevice.h +++ b/libraries/controllers/src/controllers/InputDevice.h @@ -31,6 +31,12 @@ namespace controller { class Endpoint; using EndpointPointer = std::shared_ptr; +enum Hand { + LEFT = 0, + RIGHT, + BOTH +}; + // NOTE: If something inherits from both InputDevice and InputPlugin, InputPlugin must go first. // e.g. class Example : public InputPlugin, public InputDevice // instead of class Example : public InputDevice, public InputPlugin @@ -55,6 +61,9 @@ public: const QString& getName() const { return _name; } + // By default, Input Devices do not support haptics + virtual bool triggerHapticPulse(float strength, float duration, controller::Hand hand) { return false; } + // Update call MUST be called once per simulation loop // It takes care of updating the action states and deltas virtual void update(float deltaTime, const InputCalibrationData& inputCalibrationData) {}; diff --git a/libraries/controllers/src/controllers/ScriptingInterface.cpp b/libraries/controllers/src/controllers/ScriptingInterface.cpp index c2e64ca19e..78e9378c18 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/ScriptingInterface.cpp @@ -140,6 +140,24 @@ namespace controller { return DependencyManager::get()->getActionNames(); } + bool ScriptingInterface::triggerHapticPulse(float strength, float duration, controller::Hand hand) const { + return DependencyManager::get()->triggerHapticPulse(strength, duration, hand); + } + + bool ScriptingInterface::triggerShortHapticPulse(float strength, controller::Hand hand) const { + const float SHORT_HAPTIC_DURATION_MS = 250.0f; + return DependencyManager::get()->triggerHapticPulse(strength, SHORT_HAPTIC_DURATION_MS, hand); + } + + bool ScriptingInterface::triggerHapticPulseOnDevice(unsigned int device, float strength, float duration, controller::Hand hand) const { + return DependencyManager::get()->triggerHapticPulseOnDevice(device, strength, duration, hand); + } + + bool ScriptingInterface::triggerShortHapticPulseOnDevice(unsigned int device, float strength, controller::Hand hand) const { + const float SHORT_HAPTIC_DURATION_MS = 250.0f; + return DependencyManager::get()->triggerHapticPulseOnDevice(device, strength, SHORT_HAPTIC_DURATION_MS, hand); + } + void ScriptingInterface::updateMaps() { QVariantMap newHardware; auto userInputMapper = DependencyManager::get(); diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h index f30212a09e..713e864561 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.h +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -84,6 +84,11 @@ namespace controller { Q_INVOKABLE Pose getPoseValue(const int& source) const; Q_INVOKABLE Pose getPoseValue(StandardPoseChannel source, uint16_t device = 0) const; + Q_INVOKABLE bool triggerHapticPulse(float strength, float duration, controller::Hand hand = BOTH) const; + Q_INVOKABLE bool triggerShortHapticPulse(float strength, controller::Hand hand = BOTH) const; + Q_INVOKABLE bool triggerHapticPulseOnDevice(unsigned int device, float strength, float duration, controller::Hand hand = BOTH) const; + Q_INVOKABLE bool triggerShortHapticPulseOnDevice(unsigned int device, float strength, controller::Hand hand = BOTH) const; + Q_INVOKABLE QObject* newMapping(const QString& mappingName = QUuid::createUuid().toString()); Q_INVOKABLE void enableMapping(const QString& mappingName, bool enable = true); Q_INVOKABLE void disableMapping(const QString& mappingName) { enableMapping(mappingName, false); } diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index c1ee3ce36c..921f78c613 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -336,10 +336,28 @@ QVector UserInputMapper::getActionNames() const { return result; } +bool UserInputMapper::triggerHapticPulse(float strength, float duration, controller::Hand hand) { + Locker locker(_lock); + bool toReturn = false; + for (auto device : _registeredDevices) { + toReturn = toReturn || device.second->triggerHapticPulse(strength, duration, hand); + } + return toReturn; +} + +bool UserInputMapper::triggerHapticPulseOnDevice(uint16 deviceID, float strength, float duration, controller::Hand hand) { + Locker locker(_lock); + if (_registeredDevices.find(deviceID) != _registeredDevices.end()) { + return _registeredDevices[deviceID]->triggerHapticPulse(strength, duration, hand); + } + return false; +} + int actionMetaTypeId = qRegisterMetaType(); int inputMetaTypeId = qRegisterMetaType(); int inputPairMetaTypeId = qRegisterMetaType(); int poseMetaTypeId = qRegisterMetaType("Pose"); +int handMetaTypeId = qRegisterMetaType(); QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input); void inputFromScriptValue(const QScriptValue& object, Input& input); @@ -347,6 +365,8 @@ QScriptValue actionToScriptValue(QScriptEngine* engine, const Action& action); void actionFromScriptValue(const QScriptValue& object, Action& action); QScriptValue inputPairToScriptValue(QScriptEngine* engine, const Input::NamedPair& inputPair); void inputPairFromScriptValue(const QScriptValue& object, Input::NamedPair& inputPair); +QScriptValue handToScriptValue(QScriptEngine* engine, const controller::Hand& hand); +void handFromScriptValue(const QScriptValue& object, controller::Hand& hand); QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input) { QScriptValue obj = engine->newObject(); @@ -385,12 +405,21 @@ void inputPairFromScriptValue(const QScriptValue& object, Input::NamedPair& inpu inputPair.second = QString(object.property("inputName").toVariant().toString()); } +QScriptValue handToScriptValue(QScriptEngine* engine, const controller::Hand& hand) { + return engine->newVariant((int)hand); +} + +void handFromScriptValue(const QScriptValue& object, controller::Hand& hand) { + hand = Hand(object.toVariant().toInt()); +} + void UserInputMapper::registerControllerTypes(QScriptEngine* engine) { qScriptRegisterSequenceMetaType >(engine); qScriptRegisterSequenceMetaType(engine); qScriptRegisterMetaType(engine, actionToScriptValue, actionFromScriptValue); qScriptRegisterMetaType(engine, inputToScriptValue, inputFromScriptValue); qScriptRegisterMetaType(engine, inputPairToScriptValue, inputPairFromScriptValue); + qScriptRegisterMetaType(engine, handToScriptValue, handFromScriptValue); qScriptRegisterMetaType(engine, Pose::toScriptValue, Pose::fromScriptValue); } diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 9c79415b6e..811b621b09 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -89,6 +89,8 @@ namespace controller { void setActionState(Action action, float value) { _actionStates[toInt(action)] = value; } void deltaActionState(Action action, float delta) { _actionStates[toInt(action)] += delta; } void setActionState(Action action, const Pose& value) { _poseStates[toInt(action)] = value; } + bool triggerHapticPulse(float strength, float duration, controller::Hand hand); + bool triggerHapticPulseOnDevice(uint16 deviceID, float strength, float duration, controller::Hand hand); static Input makeStandardInput(controller::StandardButtonChannel button); static Input makeStandardInput(controller::StandardAxisChannel axis); @@ -199,6 +201,7 @@ Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(controller::Input) Q_DECLARE_METATYPE(controller::Action) Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(controller::Hand) // Cheating. using UserInputMapper = controller::UserInputMapper; diff --git a/plugins/hifiSdl2/src/Joystick.cpp b/plugins/hifiSdl2/src/Joystick.cpp index a109656489..e88fe8b958 100644 --- a/plugins/hifiSdl2/src/Joystick.cpp +++ b/plugins/hifiSdl2/src/Joystick.cpp @@ -21,9 +21,16 @@ Joystick::Joystick(SDL_JoystickID instanceId, SDL_GameController* sdlGameControl InputDevice("GamePad"), _sdlGameController(sdlGameController), _sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)), + _sdlHaptic(SDL_HapticOpenFromJoystick(_sdlJoystick)), _instanceId(instanceId) { - + if (!_sdlHaptic) { + qDebug() << "SDL Haptic Open Failure: " << QString(SDL_GetError()); + } else { + if (SDL_HapticRumbleInit(_sdlHaptic) != 0) { + qDebug() << "SDL Haptic Rumble Init Failure: " << QString(SDL_GetError()); + } + } } Joystick::~Joystick() { @@ -31,6 +38,9 @@ Joystick::~Joystick() { } void Joystick::closeJoystick() { + if (_sdlHaptic) { + SDL_HapticClose(_sdlHaptic); + } SDL_GameControllerClose(_sdlGameController); } @@ -62,6 +72,13 @@ void Joystick::handleButtonEvent(const SDL_ControllerButtonEvent& event) { } } +bool Joystick::triggerHapticPulse(float strength, float duration, controller::Hand hand) { + if (SDL_HapticRumblePlay(_sdlHaptic, strength, duration) != 0) { + return false; + } + return true; +} + controller::Input::NamedVector Joystick::getAvailableInputs() const { using namespace controller; static const Input::NamedVector availableInputs{ diff --git a/plugins/hifiSdl2/src/Joystick.h b/plugins/hifiSdl2/src/Joystick.h index e2eaeaef8b..b37d22de39 100644 --- a/plugins/hifiSdl2/src/Joystick.h +++ b/plugins/hifiSdl2/src/Joystick.h @@ -36,6 +36,8 @@ public: virtual QString getDefaultMappingConfig() const override; virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void focusOutEvent() override; + + bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; Joystick() : InputDevice("GamePad") {} ~Joystick(); @@ -52,6 +54,7 @@ public: private: SDL_GameController* _sdlGameController; SDL_Joystick* _sdlJoystick; + SDL_Haptic* _sdlHaptic; SDL_JoystickID _instanceId; }; diff --git a/plugins/hifiSdl2/src/SDL2Manager.cpp b/plugins/hifiSdl2/src/SDL2Manager.cpp index 0bdb68f830..09e783864c 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.cpp +++ b/plugins/hifiSdl2/src/SDL2Manager.cpp @@ -49,7 +49,7 @@ SDL_JoystickID SDL2Manager::getInstanceId(SDL_GameController* controller) { } void SDL2Manager::init() { - bool initSuccess = (SDL_Init(SDL_INIT_GAMECONTROLLER) == 0); + bool initSuccess = (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) == 0); if (initSuccess) { int joystickCount = SDL_NumJoysticks(); diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 9eadb83ea7..9f0e76363b 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -105,6 +105,12 @@ void OculusControllerManager::pluginFocusOutEvent() { } } +void OculusControllerManager::stopHapticPulse(bool leftHand) { + if (_touch) { + _touch->stopHapticPulse(leftHand); + } +} + using namespace controller; static const std::vector> BUTTON_MAP { { @@ -212,6 +218,21 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control _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() { @@ -309,6 +330,41 @@ void OculusControllerManager::TouchDevice::handlePose(float deltaTime, pose = pose.transform(controllerToAvatar); } +bool OculusControllerManager::TouchDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) { + Locker locker(_lock); + bool toReturn = true; + 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(_parent._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(_parent._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_SetControllerVibration(_parent._session, handType, 0.0f, 0.0f); +} + controller::Input::NamedVector OculusControllerManager::TouchDevice::getAvailableInputs() const { using namespace controller; QVector availableInputs{ diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h index 980e1286f8..3c5cdeb7c6 100644 --- a/plugins/oculus/src/OculusControllerManager.h +++ b/plugins/oculus/src/OculusControllerManager.h @@ -32,6 +32,9 @@ public: void pluginFocusOutEvent() override; void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; +private slots: + void stopHapticPulse(bool leftHand); + private: class OculusInputDevice : public controller::InputDevice { public: @@ -64,9 +67,24 @@ private: void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void focusOutEvent() override; + bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; + private: + void stopHapticPulse(bool leftHand); void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose); int _trackedControllers { 0 }; + + // perform an action when the TouchDevice mutex is acquired. + using Locker = std::unique_lock; + template + void withLock(F&& f) { Locker locker(_lock); f(); } + + float _leftHapticDuration { 0.0f }; + float _leftHapticStrength { 0.0f }; + float _rightHapticDuration { 0.0f }; + float _rightHapticStrength { 0.0f }; + mutable std::recursive_mutex _lock; + friend class OculusControllerManager; }; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 830448cf7f..0d2cdc940a 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -125,7 +125,6 @@ bool ViveControllerManager::activate() { auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(_inputDevice); _registeredWithInputMapper = true; - return true; } @@ -250,6 +249,17 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle handleHandController(deltaTime, leftHandDeviceIndex, inputCalibrationData, true); handleHandController(deltaTime, rightHandDeviceIndex, inputCalibrationData, false); + // handle haptics + { + Locker locker(_lock); + if (_leftHapticDuration > 0.0f) { + hapticsHelper(deltaTime, true); + } + if (_rightHapticDuration > 0.0f) { + hapticsHelper(deltaTime, false); + } + } + int numTrackedControllers = 0; if (leftHandDeviceIndex != vr::k_unTrackedDeviceIndexInvalid) { numTrackedControllers++; @@ -447,6 +457,55 @@ void ViveControllerManager::InputDevice::handlePoseEvent(float deltaTime, const _poseStateMap[isLeftHand ? controller::LEFT_HAND : controller::RIGHT_HAND] = avatarPose.transform(controllerToAvatar); } +bool ViveControllerManager::InputDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) { + Locker locker(_lock); + if (hand == controller::BOTH || hand == controller::LEFT) { + if (strength == 0.0f) { + _leftHapticStrength = 0.0f; + _leftHapticDuration = 0.0f; + } else { + _leftHapticStrength = (duration > _leftHapticDuration) ? strength : _leftHapticStrength; + _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; + _rightHapticDuration = std::max(duration, _rightHapticDuration); + } + } + return true; +} + +void ViveControllerManager::InputDevice::hapticsHelper(float deltaTime, bool leftHand) { + auto handRole = leftHand ? vr::TrackedControllerRole_LeftHand : vr::TrackedControllerRole_RightHand; + auto deviceIndex = _system->GetTrackedDeviceIndexForControllerRole(handRole); + + if (_system->IsTrackedDeviceConnected(deviceIndex) && + _system->GetTrackedDeviceClass(deviceIndex) == vr::TrackedDeviceClass_Controller && + _trackedDevicePose[deviceIndex].bPoseIsValid) { + float strength = leftHand ? _leftHapticStrength : _rightHapticStrength; + float duration = leftHand ? _leftHapticDuration : _rightHapticDuration; + + // Vive Controllers only support duration up to 4 ms, which is short enough that any variation feels more like strength + const float MAX_HAPTIC_TIME = 3999.0f; // in microseconds + float hapticTime = strength * MAX_HAPTIC_TIME; + if (hapticTime < duration * 1000.0f) { + _system->TriggerHapticPulse(deviceIndex, 0, hapticTime); + } + + float remainingHapticTime = duration - (hapticTime / 1000.0f + deltaTime * 1000.0f); // in milliseconds + if (leftHand) { + _leftHapticDuration = remainingHapticTime; + } else { + _rightHapticDuration = remainingHapticTime; + } + } +} + controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableInputs() const { using namespace controller; QVector availableInputs{ diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index bd5d4a39f4..3a2ef1573f 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -56,6 +56,9 @@ private: void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void focusOutEvent() override; + bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; + void hapticsHelper(float deltaTime, bool leftHand); + void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand); void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand); @@ -90,8 +93,19 @@ private: FilteredStick _filteredLeftStick; FilteredStick _filteredRightStick; + // perform an action when the InputDevice mutex is acquired. + using Locker = std::unique_lock; + template + void withLock(F&& f) { Locker locker(_lock); f(); } + int _trackedControllers { 0 }; vr::IVRSystem*& _system; + float _leftHapticStrength { 0.0f }; + float _leftHapticDuration { 0.0f }; + float _rightHapticStrength { 0.0f }; + float _rightHapticDuration { 0.0f }; + mutable std::recursive_mutex _lock; + friend class ViveControllerManager; };