From 09a4e0eaa88b668d4fc76f4e71a1fb4b082c3162 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 10 May 2016 19:32:08 -0700 Subject: [PATCH] Fix for vive controllers sometimes not working * Fixed bug with input devices that where added, removed then re-added. The default mappings were being ignored on the second add. * Fixed potential crash when hardware inputPlugin device poses were polled from the JavaScript thread by taking the UserInputManager lock during pluginUpdate. * Renamed Controller.Hardware.Vive.LB & RB to LeftGrip and RightGrip, to better match Oculus touch. * Updated resource/controller/vive.json to reflect this new mapping. * Exposed touch pad capacitive touch events to JavaScript as Controller.Hardware.Vive.LSTouch and RSTouch. * Added viveTouchpadTest.js script to test LSTouch and RSTouch events. --- interface/resources/controllers/vive.json | 4 +- .../src/controllers/UserInputMapper.cpp | 33 ++++++-- .../src/controllers/UserInputMapper.h | 11 ++- .../src/input-plugins/KeyboardMouseDevice.cpp | 8 +- plugins/hifiNeuron/src/NeuronPlugin.cpp | 7 +- plugins/hifiSixense/src/SixenseManager.cpp | 7 +- plugins/openvr/src/ViveControllerManager.cpp | 59 ++++++++++----- plugins/openvr/src/ViveControllerManager.h | 2 +- scripts/developer/tests/viveTouchpadTest.js | 75 +++++++++++++++++++ 9 files changed, 172 insertions(+), 34 deletions(-) create mode 100644 scripts/developer/tests/viveTouchpadTest.js diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 1f71658946..e534c16b50 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -5,14 +5,14 @@ { "from": "Vive.LX", "when": "Vive.LS", "to": "Standard.LX" }, { "from": "Vive.LT", "to": "Standard.LT" }, - { "from": "Vive.LB", "to": "Standard.LB" }, + { "from": "Vive.LeftGrip", "to": "Standard.LB" }, { "from": "Vive.LS", "to": "Standard.LS" }, { "from": "Vive.RY", "when": "Vive.RS", "filters": "invert", "to": "Standard.RY" }, { "from": "Vive.RX", "when": "Vive.RS", "to": "Standard.RX" }, { "from": "Vive.RT", "to": "Standard.RT" }, - { "from": "Vive.RB", "to": "Standard.RB" }, + { "from": "Vive.RightGrip", "to": "Standard.RB" }, { "from": "Vive.RS", "to": "Standard.RS" }, { "from": "Vive.LeftApplicationMenu", "to": "Standard.Back" }, diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 2b7d837aa5..c0d3ff40c0 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -58,7 +58,7 @@ controller::UserInputMapper::UserInputMapper() { namespace controller { - + UserInputMapper::~UserInputMapper() { } @@ -80,6 +80,7 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) { recordDeviceOfType(device->getName()); qCDebug(controllers) << "Registered input device <" << device->getName() << "> deviceID = " << deviceID; + for (const auto& inputMapping : device->getAvailableInputs()) { const auto& input = inputMapping.first; // Ignore aliases @@ -102,6 +103,7 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) { } _registeredDevices[deviceID] = device; + auto mapping = loadMappings(device->getDefaultMappingConfigs()); if (mapping) { _mappingsByDevice[deviceID] = mapping; @@ -111,15 +113,21 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) { emit hardwareChanged(); } -// FIXME remove the associated device mappings void UserInputMapper::removeDevice(int deviceID) { + Locker locker(_lock); auto proxyEntry = _registeredDevices.find(deviceID); + if (_registeredDevices.end() == proxyEntry) { qCWarning(controllers) << "Attempted to remove unknown device " << deviceID; return; } - auto proxy = proxyEntry->second; + + auto device = proxyEntry->second; + qCDebug(controllers) << "Unregistering input device <" << device->getName() << "> deviceID = " << deviceID; + + unloadMappings(device->getDefaultMappingConfigs()); + auto mappingsEntry = _mappingsByDevice.find(deviceID); if (_mappingsByDevice.end() != mappingsEntry) { disableMapping(mappingsEntry->second); @@ -244,7 +252,7 @@ void UserInputMapper::update(float deltaTime) { for (auto& channel : _actionStates) { channel = 0.0f; } - + for (auto& channel : _poseStates) { channel = Pose(); } @@ -705,11 +713,10 @@ Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile, bool enab return Mapping::Pointer(); } // Each mapping only needs to be loaded once - static QSet loaded; - if (loaded.contains(jsonFile)) { + if (_loadedRouteJsonFiles.contains(jsonFile)) { return Mapping::Pointer(); } - loaded.insert(jsonFile); + _loadedRouteJsonFiles.insert(jsonFile); QString json; { QFile file(jsonFile); @@ -741,6 +748,18 @@ MappingPointer UserInputMapper::loadMappings(const QStringList& jsonFiles) { return result; } +void UserInputMapper::unloadMappings(const QStringList& jsonFiles) { + for (const QString& jsonFile : jsonFiles) { + unloadMapping(jsonFile); + } +} + +void UserInputMapper::unloadMapping(const QString& jsonFile) { + auto entry = _loadedRouteJsonFiles.find(jsonFile); + if (entry != _loadedRouteJsonFiles.end()) { + _loadedRouteJsonFiles.erase(entry); + } +} static const QString JSON_NAME = QStringLiteral("name"); static const QString JSON_CHANNELS = QStringLiteral("channels"); diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 95cc629c73..9c79415b6e 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -111,9 +111,18 @@ namespace controller { void loadDefaultMapping(uint16 deviceID); void enableMapping(const QString& mappingName, bool enable = true); + + void unloadMappings(const QStringList& jsonFiles); + void unloadMapping(const QString& jsonFile); + float getValue(const Input& input) const; Pose getPose(const Input& input) const; + // perform an action when the UserInputMapper mutex is acquired. + using Locker = std::unique_lock; + template + void withLock(F&& f) { Locker locker(_lock); f(); } + signals: void actionEvent(int action, float state); void inputEvent(int input, float state); @@ -177,7 +186,7 @@ namespace controller { RouteList _deviceRoutes; RouteList _standardRoutes; - using Locker = std::unique_lock; + QSet _loadedRouteJsonFiles; mutable std::recursive_mutex _lock; }; diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index 0bf398cdec..4c0240eaf7 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -20,8 +20,12 @@ const QString KeyboardMouseDevice::NAME = "Keyboard/Mouse"; -void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { - _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); +void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { + + auto userInputMapper = DependencyManager::get(); + userInputMapper->withLock([&, this]() { + _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + }); // For touch event, we need to check that the last event is not too long ago // Maybe it's a Qt issue, but the touch event sequence (begin, update, end) is not always called properly diff --git a/plugins/hifiNeuron/src/NeuronPlugin.cpp b/plugins/hifiNeuron/src/NeuronPlugin.cpp index f2b8c04827..6e2f744173 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.cpp +++ b/plugins/hifiNeuron/src/NeuronPlugin.cpp @@ -509,7 +509,12 @@ void NeuronPlugin::pluginUpdate(float deltaTime, const controller::InputCalibrat std::lock_guard guard(_jointsMutex); joints = _joints; } - _inputDevice->update(deltaTime, inputCalibrationData, joints, _prevJoints); + + auto userInputMapper = DependencyManager::get(); + userInputMapper->withLock([&, this]() { + _inputDevice->update(deltaTime, inputCalibrationData, joints, _prevJoints); + }); + _prevJoints = joints; } diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index eb55d84664..fdb7bb17fe 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -136,7 +136,12 @@ void SixenseManager::setSixenseFilter(bool filter) { void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { BAIL_IF_NOT_LOADED - _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + + auto userInputMapper = DependencyManager::get(); + userInputMapper->withLock([&, this]() { + _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + }); + if (_inputDevice->_requestReset) { _container->requestReset(); _inputDevice->_requestReset = false; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index c1ed5aa880..04abd7b398 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -211,9 +211,13 @@ void ViveControllerManager::renderHand(const controller::Pose& pose, gpu::Batch& void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { - _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); auto userInputMapper = DependencyManager::get(); + // because update mutates the internal state we need to lock + userInputMapper->withLock([&, this]() { + _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + }); + if (_inputDevice->_trackedControllers == 0 && _registeredWithInputMapper) { userInputMapper->removeDevice(_inputDevice->_deviceID); _registeredWithInputMapper = false; @@ -270,7 +274,8 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u for (uint32_t i = 0; i < vr::k_EButton_Max; ++i) { auto mask = vr::ButtonMaskFromId((vr::EVRButtonId)i); bool pressed = 0 != (controllerState.ulButtonPressed & mask); - handleButtonEvent(deltaTime, i, pressed, isLeftHand); + bool touched = 0 != (controllerState.ulButtonTouched & mask); + handleButtonEvent(deltaTime, i, pressed, touched, isLeftHand); } // process each axis @@ -314,20 +319,26 @@ enum ViveButtonChannel { // These functions do translation from the Steam IDs to the standard controller IDs -void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool isLeftHand) { - if (!pressed) { - return; - } +void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand) { using namespace controller; - if (button == vr::k_EButton_ApplicationMenu) { - _buttonPressedMap.insert(isLeftHand ? LEFT_APP_MENU : RIGHT_APP_MENU); - } else if (button == vr::k_EButton_Grip) { - _buttonPressedMap.insert(isLeftHand ? LB : RB); - } else if (button == vr::k_EButton_SteamVR_Trigger) { - _buttonPressedMap.insert(isLeftHand ? LT : RT); - } else if (button == vr::k_EButton_SteamVR_Touchpad) { - _buttonPressedMap.insert(isLeftHand ? LS : RS); + + if (pressed) { + if (button == vr::k_EButton_ApplicationMenu) { + _buttonPressedMap.insert(isLeftHand ? LEFT_APP_MENU : RIGHT_APP_MENU); + } else if (button == vr::k_EButton_Grip) { + _buttonPressedMap.insert(isLeftHand ? LEFT_GRIP : RIGHT_GRIP); + } else if (button == vr::k_EButton_SteamVR_Trigger) { + _buttonPressedMap.insert(isLeftHand ? LT : RT); + } else if (button == vr::k_EButton_SteamVR_Touchpad) { + _buttonPressedMap.insert(isLeftHand ? LS : RS); + } + } + + if (touched) { + if (button == vr::k_EButton_SteamVR_Touchpad) { + _buttonPressedMap.insert(isLeftHand ? LS_TOUCH : RS_TOUCH); + } } } @@ -424,18 +435,28 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI makePair(LY, "LY"), makePair(RX, "RX"), makePair(RY, "RY"), - // trigger analogs + + // capacitive touch on the touch pad + makePair(LS_TOUCH, "LSTouch"), + makePair(RS_TOUCH, "RSTouch"), + + // touch pad press + makePair(LS, "LS"), + makePair(RS, "RS"), + + // triggers makePair(LT, "LT"), makePair(RT, "RT"), - makePair(LB, "LB"), - makePair(RB, "RB"), + // low profile side grip button. + makePair(LEFT_GRIP, "LeftGrip"), + makePair(RIGHT_GRIP, "RightGrip"), - makePair(LS, "LS"), - makePair(RS, "RS"), + // 3d location of controller makePair(LEFT_HAND, "LeftHand"), makePair(RIGHT_HAND, "RightHand"), + // app button above trackpad. Input::NamedPair(Input(_deviceID, LEFT_APP_MENU, ChannelType::BUTTON), "LeftApplicationMenu"), Input::NamedPair(Input(_deviceID, RIGHT_APP_MENU, ChannelType::BUTTON), "RightApplicationMenu"), }; diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index d3645304c5..d55d4e726c 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -59,7 +59,7 @@ private: virtual void focusOutEvent() override; void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); - void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, 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); void handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand); diff --git a/scripts/developer/tests/viveTouchpadTest.js b/scripts/developer/tests/viveTouchpadTest.js new file mode 100644 index 0000000000..52b33f02a4 --- /dev/null +++ b/scripts/developer/tests/viveTouchpadTest.js @@ -0,0 +1,75 @@ +// +// viveTouchpadTest.js +// +// Anthony J. Thibault +// Copyright 2016 High Fidelity, Inc. +// +// An example of reading touch and move events from the vive controller touch pad. +// +// It will spawn a gray cube in front of you, then as you use the right touch pad, +// the cube should turn green and respond to the motion of your thumb on the pad. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var GRAY = {red: 57, green: 57, blue: 57}; +var GREEN = {red: 0, green: 255, blue: 0}; +var ZERO = {x: 0, y: 0, z: 0}; +var Y_AXIS = {x: 0, y: 1, x: 0}; +var ROT_Y_90 = Quat.angleAxis(Y_AXIS, 90.0); + +var boxEntity; +var boxPosition; +var boxZAxis, boxYAxis; +var prevThumbDown = false; + +function init() { + boxPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation()))); + var front = Quat.getFront(Camera.getOrientation()); + boxZAxis = Vec3.normalize(Vec3.cross(front, Y_AXIS)); + boxYAxis = Vec3.normalize(Vec3.cross(boxZAxis, front)); + + boxEntity = Entities.addEntity({ + type: "Box", + position: boxPosition, + dimentions: {x: 0.25, y: 0.25, z: 0.25}, + color: GRAY, + gravity: ZERO, + visible: true, + locked: false, + lifetime: 60000 + }); +} + +function shutdown() { + Entities.deleteEntity(boxEntity); +} +Script.scriptEnding.connect(shutdown); + +prevPose = Controller.getPoseValue(Controller.Hardware.Vive.LeftHand); + +function update(dt) { + var thumbDown = Controller.getValue(Controller.Hardware.Vive.RSTouch); + if (thumbDown) { + var x = Controller.getValue(Controller.Hardware.Vive.RX); + var y = Controller.getValue(Controller.Hardware.Vive.RY); + var xOffset = Vec3.multiply(boxZAxis, x); + var yOffset = Vec3.multiply(boxYAxis, y); + var offset = Vec3.sum(xOffset, yOffset); + Entities.editEntity(boxEntity, {position: Vec3.sum(boxPosition, offset)}); + } + if (thumbDown && !prevThumbDown) { + Entities.editEntity(boxEntity, {color: GREEN}); + } + if (!thumbDown && prevThumbDown) { + Entities.editEntity(boxEntity, {color: GRAY}); + } + prevThumbDown = thumbDown; +} + +Script.update.connect(update); + +init(); + + +