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.
This commit is contained in:
Anthony J. Thibault 2016-05-10 19:32:08 -07:00
parent 59c6a6dfea
commit 09a4e0eaa8
9 changed files with 172 additions and 34 deletions

View file

@ -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" },

View file

@ -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<QString> 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");

View file

@ -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<std::recursive_mutex>;
template <typename F>
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<std::recursive_mutex>;
QSet<QString> _loadedRouteJsonFiles;
mutable std::recursive_mutex _lock;
};

View file

@ -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<controller::UserInputMapper>();
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

View file

@ -509,7 +509,12 @@ void NeuronPlugin::pluginUpdate(float deltaTime, const controller::InputCalibrat
std::lock_guard<std::mutex> guard(_jointsMutex);
joints = _joints;
}
_inputDevice->update(deltaTime, inputCalibrationData, joints, _prevJoints);
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
userInputMapper->withLock([&, this]() {
_inputDevice->update(deltaTime, inputCalibrationData, joints, _prevJoints);
});
_prevJoints = joints;
}

View file

@ -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<controller::UserInputMapper>();
userInputMapper->withLock([&, this]() {
_inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured);
});
if (_inputDevice->_requestReset) {
_container->requestReset();
_inputDevice->_requestReset = false;

View file

@ -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<controller::UserInputMapper>();
// 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"),
};

View file

@ -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);

View file

@ -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();