Merge pull request #6147 from jherico/controllers

Controllers Branch - Wiring up step yaw
This commit is contained in:
Brad Hefta-Gaub 2015-10-22 09:45:55 -07:00
commit 7c746462fa
12 changed files with 306 additions and 300 deletions

View file

@ -1,4 +1,12 @@
ControllerTest = function() {
var standard = Controller.Standard;
var actions = Controller.Actions;
this.mappingEnabled = false;
this.mapping = Controller.newMapping();
this.mapping.from(standard.RX).to(actions.StepYaw);
this.mapping.enable();
this.mappingEnabled = true;
print("Actions");
for (var prop in Controller.Actions) {
@ -24,6 +32,9 @@ ControllerTest = function() {
}
ControllerTest.prototype.onCleanup = function() {
if (this.mappingEnabled) {
this.mapping.disable();
}
}

View file

@ -3,12 +3,12 @@ import QtQuick.Controls 1.0
import QtQuick.Layouts 1.0
import QtQuick.Dialogs 1.0
Item {
Rectangle {
id: root
property int size: 64
width: size
height: size
color: 'black'
property int controlId: 0
property real value: 0.5
property int scrollWidth: 1
@ -16,7 +16,7 @@ Item {
property real max: 1.0
property bool log: false
property real range: max - min
property color color: 'blue'
property color lineColor: 'yellow'
property bool bar: false
property real lastHeight: -1
property string label: ""
@ -49,19 +49,21 @@ Item {
Text {
anchors.top: parent.top
text: root.label
color: 'white'
}
Text {
anchors.right: parent.right
anchors.top: parent.top
text: root.max
color: 'white'
}
Text {
anchors.right: parent.right
anchors.bottom: parent.bottom
text: root.min
color: 'white'
}
function scroll() {
@ -92,7 +94,7 @@ Item {
ctx.beginPath();
ctx.lineWidth = 1
ctx.strokeStyle = root.color
ctx.strokeStyle = root.lineColor
ctx.moveTo(canvas.width - root.scrollWidth, root.lastHeight).lineTo(canvas.width, currentHeight)
ctx.stroke()
ctx.restore()

View file

@ -20,8 +20,23 @@ HifiControls.VrDialog {
property var standard: Controller.Standard
property var hydra: null
property var testMapping: null
property bool testMappingEnabled: false
property var xbox: null
function buildMapping() {
testMapping = Controller.newMapping();
testMapping.from(standard.RY).invert().to(actions.Pitch);
//testMapping.makeAxis(standard.LB, standard.RB).to(actions.Yaw);
// Step yaw takes a number of degrees
testMapping.from(standard.LB).invert().scale(15.0).to(actions.StepYaw);
testMapping.from(standard.RB).scale(15.0).to(actions.StepYaw);
testMapping.from(standard.RX).scale(15.0).to(actions.StepYaw);
}
function toggleMapping() {
testMapping.enable(!testMappingEnabled);
testMappingEnabled = !testMappingEnabled;
}
Component.onCompleted: {
enabled = true
@ -49,110 +64,18 @@ HifiControls.VrDialog {
Row {
spacing: 8
Button {
text: "Standard Mapping"
onClicked: {
var mapping = Controller.newMapping("Default");
mapping.from(standard.LX).to(actions.TranslateX);
mapping.from(standard.LY).to(actions.TranslateZ);
mapping.from(standard.RY).to(actions.Pitch);
mapping.from(standard.RX).to(actions.Yaw);
mapping.from(standard.DU).scale(0.5).to(actions.LONGITUDINAL_FORWARD);
mapping.from(standard.DD).scale(0.5).to(actions.LONGITUDINAL_BACKWARD);
mapping.from(standard.DL).scale(0.5).to(actions.LATERAL_LEFT);
mapping.from(standard.DR).scale(0.5).to(actions.LATERAL_RIGHT);
mapping.from(standard.X).to(actions.VERTICAL_DOWN);
mapping.from(standard.Y).to(actions.VERTICAL_UP);
mapping.from(standard.RT).scale(0.1).to(actions.BOOM_IN);
mapping.from(standard.LT).scale(0.1).to(actions.BOOM_OUT);
mapping.from(standard.B).to(actions.ACTION1);
mapping.from(standard.A).to(actions.ACTION2);
mapping.from(standard.RB).to(actions.SHIFT);
mapping.from(standard.Back).to(actions.TOGGLE_MUTE);
mapping.from(standard.Start).to(actions.CONTEXT_MENU);
Controller.enableMapping("Default");
enabled = false;
text = "Standard Built"
}
}
Button {
text: root.xbox ? "XBox Mapping" : "XBox not found"
property bool built: false
enabled: root.xbox && !built
text: !root.testMapping ? "Build Mapping" : (root.testMappingEnabled ? "Disable Mapping" : "Enable Mapping")
onClicked: {
var mapping = Controller.newMapping();
mapping.from(xbox.A).to(standard.A);
mapping.from(xbox.B).to(standard.B);
mapping.from(xbox.X).to(standard.X);
mapping.from(xbox.Y).to(standard.Y);
mapping.from(xbox.Up).to(standard.DU);
mapping.from(xbox.Down).to(standard.DD);
mapping.from(xbox.Left).to(standard.DL);
mapping.from(xbox.Right).to(standard.Right);
mapping.from(xbox.LB).to(standard.LB);
mapping.from(xbox.RB).to(standard.RB);
mapping.from(xbox.LS).to(standard.LS);
mapping.from(xbox.RS).to(standard.RS);
mapping.from(xbox.Start).to(standard.Start);
mapping.from(xbox.Back).to(standard.Back);
mapping.from(xbox.LY).to(standard.LY);
mapping.from(xbox.LX).to(standard.LX);
mapping.from(xbox.RY).to(standard.RY);
mapping.from(xbox.RX).to(standard.RX);
mapping.from(xbox.LT).to(standard.LT);
mapping.from(xbox.RT).to(standard.RT);
mapping.enable();
built = false;
text = "XBox Built"
if (!root.testMapping) {
root.buildMapping()
} else {
root.toggleMapping();
}
}
}
Button {
text: root.hydra ? "Hydra Mapping" : "Hydra Not Found"
property bool built: false
enabled: root.hydra && !built
onClicked: {
var mapping = Controller.newMapping();
mapping.from(hydra.LY).invert().to(standard.LY);
mapping.from(hydra.LX).to(standard.LX);
mapping.from(hydra.RY).invert().to(standard.RY);
mapping.from(hydra.RX).to(standard.RX);
mapping.from(hydra.LT).to(standard.LT);
mapping.from(hydra.RT).to(standard.RT);
mapping.enable();
built = false;
text = "Hydra Built"
}
}
Button {
text: "Test Mapping"
onClicked: {
var mapping = Controller.newMapping();
// Inverting a value
mapping.from(standard.RY).invert().to(standard.RY);
mapping.makeAxis(standard.LB, standard.RB).to(actions.Yaw);
testMapping = mapping;
enabled = false
text = "Built"
}
}
Button {
text: "Enable Mapping"
onClicked: root.testMapping.enable()
}
Button {
text: "Disable Mapping"
onClicked: root.testMapping.disable()
}
Button {
text: "Enable Mapping"
onClicked: print(Controller.getValue(root.xbox.LY));
}
}
Row {
@ -170,25 +93,32 @@ HifiControls.VrDialog {
ScrollingGraph {
controlId: Controller.Actions.Yaw
label: "Yaw"
min: -3.0
max: 3.0
size: 128
min: -2.0
max: 2.0
size: 64
}
ScrollingGraph {
controlId: Controller.Actions.YAW_LEFT
controlId: Controller.Actions.YawLeft
label: "Yaw Left"
min: -3.0
max: 3.0
size: 128
min: -2.0
max: 2.0
size: 64
}
ScrollingGraph {
controlId: Controller.Actions.YAW_RIGHT
controlId: Controller.Actions.YawRight
label: "Yaw Right"
min: -3.0
max: 3.0
size: 128
min: -2.0
max: 2.0
size: 64
}
ScrollingGraph {
controlId: Controller.Actions.StepYaw
label: "StepYaw"
min: -2.0
max: 2.0
size: 64
}
}
}

View file

@ -29,7 +29,7 @@ Item {
// Left primary
ToggleButton {
x: parent.width - width; y: parent.height - height;
controlId: root.device.RB
controlId: root.device.RightPrimaryThumb
width: 16 * root.scale; height: 16 * root.scale
}
}

View file

@ -2718,15 +2718,13 @@ void Application::update(float deltaTime) {
}
// Transfer the user inputs to the driveKeys
// FIXME can we drop drive keys and just have the avatar read the action states directly?
myAvatar->clearDriveKeys();
if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) {
if (!_controllerScriptingInterface->areActionsCaptured()) {
myAvatar->setDriveKeys(FWD, userInputMapper->getActionState(controller::Action::LONGITUDINAL_FORWARD));
myAvatar->setDriveKeys(BACK, userInputMapper->getActionState(controller::Action::LONGITUDINAL_BACKWARD));
myAvatar->setDriveKeys(UP, userInputMapper->getActionState(controller::Action::VERTICAL_UP));
myAvatar->setDriveKeys(DOWN, userInputMapper->getActionState(controller::Action::VERTICAL_DOWN));
myAvatar->setDriveKeys(LEFT, userInputMapper->getActionState(controller::Action::LATERAL_LEFT));
myAvatar->setDriveKeys(RIGHT, userInputMapper->getActionState(controller::Action::LATERAL_RIGHT));
myAvatar->setDriveKeys(TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z));
myAvatar->setDriveKeys(TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y));
myAvatar->setDriveKeys(TRANSLATE_X, userInputMapper->getActionState(controller::Action::TRANSLATE_X));
if (deltaTime > FLT_EPSILON) {
// For rotations what we really want are meausures of "angles per second" (in order to prevent
// fps-dependent spin rates) so we need to scale the units of the controller contribution.
@ -2734,14 +2732,12 @@ void Application::update(float deltaTime) {
// controllers to provide a delta_per_second value rather than a raw delta.)
const float EXPECTED_FRAME_RATE = 60.0f;
float timeFactor = EXPECTED_FRAME_RATE * deltaTime;
myAvatar->setDriveKeys(ROT_UP, userInputMapper->getActionState(controller::Action::PITCH_UP) / timeFactor);
myAvatar->setDriveKeys(ROT_DOWN, userInputMapper->getActionState(controller::Action::PITCH_DOWN) / timeFactor);
myAvatar->setDriveKeys(ROT_LEFT, userInputMapper->getActionState(controller::Action::YAW_LEFT) / timeFactor);
myAvatar->setDriveKeys(ROT_RIGHT, userInputMapper->getActionState(controller::Action::YAW_RIGHT) / timeFactor);
myAvatar->setDriveKeys(PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH) / timeFactor);
myAvatar->setDriveKeys(YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW) / timeFactor);
myAvatar->setDriveKeys(STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW) / timeFactor);
}
}
myAvatar->setDriveKeys(BOOM_IN, userInputMapper->getActionState(controller::Action::BOOM_IN));
myAvatar->setDriveKeys(BOOM_OUT, userInputMapper->getActionState(controller::Action::BOOM_OUT));
myAvatar->setDriveKeys(ZOOM, userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z));
}
controller::Pose leftHand = userInputMapper->getPoseState(controller::Action::LEFT_HAND);
controller::Pose rightHand = userInputMapper->getPoseState(controller::Action::RIGHT_HAND);

View file

@ -43,21 +43,6 @@ static const float BILLBOARD_DISTANCE = 5.56f; // meters
extern const float CHAT_MESSAGE_SCALE;
extern const float CHAT_MESSAGE_HEIGHT;
enum DriveKeys {
FWD = 0,
BACK,
LEFT,
RIGHT,
UP,
DOWN,
ROT_LEFT,
ROT_RIGHT,
ROT_UP,
ROT_DOWN,
BOOM_IN,
BOOM_OUT,
MAX_DRIVE_KEYS
};
enum ScreenTintLayer {
SCREEN_TINT_BEFORE_LANDSCAPE = 0,

View file

@ -53,6 +53,7 @@
using namespace std;
static quint64 COMFORT_MODE_PULSE_TIMING = USECS_PER_SECOND / 2; // turn once per half second
const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f);
const float YAW_SPEED = 150.0f; // degrees/sec
const float PITCH_SPEED = 100.0f; // degrees/sec
@ -240,8 +241,31 @@ void MyAvatar::simulate(float deltaTime) {
{
PerformanceTimer perfTimer("transform");
bool stepAction = false;
// When there are no step values, we zero out the last step pulse.
// This allows a user to do faster snapping by tapping a control
for (int i = STEP_TRANSLATE_X; !stepAction && i <= STEP_YAW; ++i) {
if (_driveKeys[i] != 0.0f) {
stepAction = true;
}
}
quint64 now = usecTimestampNow();
quint64 pulseDeltaTime = now - _lastStepPulse;
if (!stepAction) {
_lastStepPulse = 0;
}
if (stepAction && pulseDeltaTime > COMFORT_MODE_PULSE_TIMING) {
_pulseUpdate = true;
}
updateOrientation(deltaTime);
updatePosition(deltaTime);
if (_pulseUpdate) {
_lastStepPulse = now;
_pulseUpdate = false;
}
}
{
@ -1554,69 +1578,44 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
void MyAvatar::updateOrientation(float deltaTime) {
// Smoothly rotate body with arrow keys
float targetSpeed = 0.0f;
// FIXME - this comfort mode code is a total hack, remove it when we have new input mapping
bool isComfortMode = Menu::getInstance()->isOptionChecked(MenuOption::ComfortMode);
bool isHMDMode = qApp->getAvatarUpdater()->isHMDMode();
if (!isHMDMode || !isComfortMode) {
targetSpeed = (_driveKeys[ROT_LEFT] - _driveKeys[ROT_RIGHT]) * YAW_SPEED;
if (targetSpeed != 0.0f) {
const float ROTATION_RAMP_TIMESCALE = 0.1f;
float blend = deltaTime / ROTATION_RAMP_TIMESCALE;
if (blend > 1.0f) {
blend = 1.0f;
}
_bodyYawDelta = (1.0f - blend) * _bodyYawDelta + blend * targetSpeed;
} else if (_bodyYawDelta != 0.0f) {
// attenuate body rotation speed
const float ROTATION_DECAY_TIMESCALE = 0.05f;
float attenuation = 1.0f - deltaTime / ROTATION_DECAY_TIMESCALE;
if (attenuation < 0.0f) {
attenuation = 0.0f;
}
_bodyYawDelta *= attenuation;
float MINIMUM_ROTATION_RATE = 2.0f;
if (fabsf(_bodyYawDelta) < MINIMUM_ROTATION_RATE) {
_bodyYawDelta = 0.0f;
}
float targetSpeed = _driveKeys[YAW] * YAW_SPEED;
if (targetSpeed != 0.0f) {
const float ROTATION_RAMP_TIMESCALE = 0.1f;
float blend = deltaTime / ROTATION_RAMP_TIMESCALE;
if (blend > 1.0f) {
blend = 1.0f;
}
_bodyYawDelta = (1.0f - blend) * _bodyYawDelta + blend * targetSpeed;
} else if (_bodyYawDelta != 0.0f) {
// attenuate body rotation speed
const float ROTATION_DECAY_TIMESCALE = 0.05f;
float attenuation = 1.0f - deltaTime / ROTATION_DECAY_TIMESCALE;
if (attenuation < 0.0f) {
attenuation = 0.0f;
}
_bodyYawDelta *= attenuation;
// update body orientation by movement inputs
setOrientation(getOrientation() *
glm::quat(glm::radians(glm::vec3(0.0f, _bodyYawDelta * deltaTime, 0.0f))));
} else {
// Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll
// get an instantaneous 15 degree turn. If you keep holding the key down you'll get another
// snap turn every half second.
_bodyYawDelta = 0.0f;
static quint64 lastPulse = 0;
quint64 now = usecTimestampNow();
quint64 COMFORT_MODE_PULSE_TIMING = USECS_PER_SECOND / 2; // turn once per half second
float driveLeft = _driveKeys[ROT_LEFT];
float driveRight= _driveKeys[ROT_RIGHT];
if ((driveLeft != 0.0f || driveRight != 0.0f) && (now - lastPulse > COMFORT_MODE_PULSE_TIMING)) {
lastPulse = now;
const float SNAP_TURN_DELTA = 15.0f; // degrees
float direction = (driveLeft - driveRight) < 0.0f ? -1.0f : 1.0f;
float turnAmount = direction * SNAP_TURN_DELTA;
// update body orientation by movement inputs
setOrientation(getOrientation() *
glm::quat(glm::radians(glm::vec3(0.0f, turnAmount, 0.0f))));
float MINIMUM_ROTATION_RATE = 2.0f;
if (fabsf(_bodyYawDelta) < MINIMUM_ROTATION_RATE) {
_bodyYawDelta = 0.0f;
}
}
getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * deltaTime);
float totalBodyYaw = _bodyYawDelta * deltaTime;
// Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll
// get an instantaneous 15 degree turn. If you keep holding the key down you'll get another
// snap turn every half second.
quint64 now = usecTimestampNow();
if (_driveKeys[STEP_YAW] != 0.0f && now - _lastStepPulse > COMFORT_MODE_PULSE_TIMING) {
totalBodyYaw += _driveKeys[STEP_YAW];
}
// update body orientation by movement inputs
setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
getHead()->setBasePitch(getHead()->getBasePitch() + _driveKeys[PITCH] * PITCH_SPEED * deltaTime);
if (qApp->getAvatarUpdater()->isHMDMode()) {
glm::quat orientation = glm::quat_cast(getSensorToWorldMatrix()) * getHMDSensorOrientation();
@ -1676,14 +1675,18 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
float motorEfficiency = glm::clamp(deltaTime / timescale, 0.0f, 1.0f);
glm::vec3 newLocalVelocity = localVelocity;
float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) +
(fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) +
fabsf(_driveKeys[UP] - _driveKeys[DOWN]);
float stepControllerInput = fabsf(_driveKeys[STEP_TRANSLATE_Z]) + fabsf(_driveKeys[STEP_TRANSLATE_Z]) + fabsf(_driveKeys[STEP_TRANSLATE_Z]);
quint64 now = usecTimestampNow();
// FIXME how do I implement step translation as well?
if (stepControllerInput && now - _lastStepPulse > COMFORT_MODE_PULSE_TIMING) {
}
float keyboardInput = fabsf(_driveKeys[TRANSLATE_Z]) + fabsf(_driveKeys[TRANSLATE_X]) + fabsf(_driveKeys[TRANSLATE_Y]);
if (keyboardInput) {
// Compute keyboard input
glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT;
glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT;
glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP;
glm::vec3 front = (_driveKeys[TRANSLATE_Z]) * IDENTITY_FRONT;
glm::vec3 right = (_driveKeys[TRANSLATE_X]) * IDENTITY_RIGHT;
glm::vec3 up = (_driveKeys[TRANSLATE_Y]) * IDENTITY_UP;
glm::vec3 direction = front + right + up;
float directionLength = glm::length(direction);
@ -1734,7 +1737,7 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
}
}
float boomChange = _driveKeys[BOOM_OUT] - _driveKeys[BOOM_IN];
float boomChange = _driveKeys[ZOOM];
_boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
_boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX);
@ -1983,7 +1986,7 @@ void MyAvatar::clearDriveKeys() {
}
void MyAvatar::relayDriveKeysToCharacterController() {
if (_driveKeys[UP] > 0.0f) {
if (_driveKeys[TRANSLATE_Y] > 0.0f) {
_characterController.jump();
}
}

View file

@ -21,6 +21,20 @@
class ModelItemID;
enum DriveKeys {
TRANSLATE_X = 0,
TRANSLATE_Y,
TRANSLATE_Z,
YAW,
STEP_TRANSLATE_X,
STEP_TRANSLATE_Y,
STEP_TRANSLATE_Z,
STEP_YAW,
PITCH,
ZOOM,
MAX_DRIVE_KEYS
};
enum eyeContactTarget {
LEFT_EYE,
RIGHT_EYE,
@ -376,6 +390,8 @@ private:
AtRestDetector _hmdAtRestDetector;
glm::vec3 _lastPosition;
bool _lastIsMoving = false;
quint64 _lastStepPulse = 0;
bool _pulseUpdate { false };
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -42,6 +42,12 @@ namespace controller {
makeAxisPair(Action::ROLL, "Roll"),
makeAxisPair(Action::PITCH, "Pitch"),
makeAxisPair(Action::YAW, "Yaw"),
makeAxisPair(Action::STEP_YAW, "StepYaw"),
makeAxisPair(Action::STEP_PITCH, "StepPitch"),
makeAxisPair(Action::STEP_ROLL, "StepRoll"),
makeAxisPair(Action::STEP_TRANSLATE_X, "StepTranslateX"),
makeAxisPair(Action::STEP_TRANSLATE_X, "StepTranslateY"),
makeAxisPair(Action::STEP_TRANSLATE_X, "StepTranslateZ"),
makeAxisPair(Action::LONGITUDINAL_BACKWARD, "Backward"),
makeAxisPair(Action::LONGITUDINAL_FORWARD, "Forward"),
makeAxisPair(Action::LATERAL_LEFT, "StrafeLeft"),
@ -67,7 +73,6 @@ namespace controller {
makeButtonPair(Action::CONTEXT_MENU, "ContextMenu"),
makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"),
// Deprecated aliases
// FIXME remove after we port all scripts
makeAxisPair(Action::LONGITUDINAL_BACKWARD, "LONGITUDINAL_BACKWARD"),

View file

@ -27,6 +27,16 @@ enum class Action {
ROTATE_Y, YAW = ROTATE_Y,
ROTATE_Z, ROLL = ROTATE_Z,
STEP_YAW,
// FIXME does this have a use case?
STEP_PITCH,
// FIXME does this have a use case?
STEP_ROLL,
STEP_TRANSLATE_X,
STEP_TRANSLATE_Y,
STEP_TRANSLATE_Z,
TRANSLATE_CAMERA_Z,
NUM_COMBINED_AXES,

View file

@ -29,7 +29,6 @@ namespace controller {
// Default contruct allocate the poutput size with the current hardcoded action channels
controller::UserInputMapper::UserInputMapper() {
_activeMappings.push_back(_defaultMapping);
_standardController = std::make_shared<StandardController>();
registerDevice(new ActionsDevice());
registerDevice(_standardController.get());
@ -317,6 +316,7 @@ int UserInputMapper::recordDeviceOfType(const QString& deviceName) {
}
void UserInputMapper::registerDevice(InputDevice* device) {
Locker locker(_lock);
if (device->_deviceID == Input::INVALID_DEVICE) {
device->_deviceID = getFreeDeviceID();
}
@ -354,13 +354,7 @@ void UserInputMapper::registerDevice(InputDevice* device) {
auto mapping = loadMapping(device->getDefaultMappingConfig());
if (mapping) {
_mappingsByDevice[deviceID] = mapping;
auto& defaultRoutes = _defaultMapping->routes;
// New routes for a device get injected IN FRONT of existing routes. Routes
// are processed in order so this ensures that the standard -> action processing
// takes place after all of the hardware -> standard or hardware -> action processing
// because standard -> action is the first set of routes added.
defaultRoutes.insert(defaultRoutes.begin(), mapping->routes.begin(), mapping->routes.end());
enableMapping(mapping);
}
emit hardwareChanged();
@ -368,6 +362,7 @@ void UserInputMapper::registerDevice(InputDevice* device) {
// 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;
@ -376,15 +371,7 @@ void UserInputMapper::removeDevice(int deviceID) {
auto proxy = proxyEntry->second;
auto mappingsEntry = _mappingsByDevice.find(deviceID);
if (_mappingsByDevice.end() != mappingsEntry) {
const auto& mapping = mappingsEntry->second;
const auto& deviceRoutes = mapping->routes;
std::set<Route::Pointer> routeSet(deviceRoutes.begin(), deviceRoutes.end());
auto& defaultRoutes = _defaultMapping->routes;
std::remove_if(defaultRoutes.begin(), defaultRoutes.end(), [&](Route::Pointer route)->bool {
return routeSet.count(route) != 0;
});
disableMapping(mappingsEntry->second);
_mappingsByDevice.erase(mappingsEntry);
}
@ -395,6 +382,7 @@ void UserInputMapper::removeDevice(int deviceID) {
DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) {
Locker locker(_lock);
auto device = _registeredDevices.find(input.getDevice());
if (device != _registeredDevices.end()) {
return (device->second);
@ -404,6 +392,7 @@ DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) {
}
QString UserInputMapper::getDeviceName(uint16 deviceID) {
Locker locker(_lock);
if (_registeredDevices.find(deviceID) != _registeredDevices.end()) {
return _registeredDevices[deviceID]->_name;
}
@ -411,6 +400,7 @@ QString UserInputMapper::getDeviceName(uint16 deviceID) {
}
int UserInputMapper::findDevice(QString name) const {
Locker locker(_lock);
for (auto device : _registeredDevices) {
if (device.second->_name == name) {
return device.first;
@ -420,6 +410,7 @@ int UserInputMapper::findDevice(QString name) const {
}
QVector<QString> UserInputMapper::getDeviceNames() {
Locker locker(_lock);
QVector<QString> result;
for (auto device : _registeredDevices) {
QString deviceName = device.second->_name.split(" (")[0];
@ -433,6 +424,7 @@ int UserInputMapper::findAction(const QString& actionName) const {
}
Input UserInputMapper::findDeviceInput(const QString& inputName) const {
Locker locker(_lock);
// Split the full input name as such: deviceName.inputName
auto names = inputName.split('.');
@ -472,6 +464,7 @@ void fixBisectedAxis(float& full, float& negative, float& positive) {
}
void UserInputMapper::update(float deltaTime) {
Locker locker(_lock);
// Reset the axis state for next loop
for (auto& channel : _actionStates) {
channel = 0.0f;
@ -505,11 +498,13 @@ void UserInputMapper::update(float deltaTime) {
}
Input::NamedVector UserInputMapper::getAvailableInputs(uint16 deviceID) const {
Locker locker(_lock);
auto iterator = _registeredDevices.find(deviceID);
return iterator->second->getAvailabeInputs();
}
QVector<Action> UserInputMapper::getAllActions() const {
Locker locker(_lock);
QVector<Action> actions;
for (auto i = 0; i < toInt(Action::NUM_ACTIONS); i++) {
actions.append(Action(i));
@ -518,6 +513,7 @@ QVector<Action> UserInputMapper::getAllActions() const {
}
QString UserInputMapper::getActionName(Action action) const {
Locker locker(_lock);
for (auto actionPair : getActionInputs()) {
if (actionPair.first.channel == toInt(action)) {
return actionPair.second;
@ -528,6 +524,7 @@ QString UserInputMapper::getActionName(Action action) const {
QVector<QString> UserInputMapper::getActionNames() const {
Locker locker(_lock);
QVector<QString> result;
for (auto actionPair : getActionInputs()) {
result << actionPair.second;
@ -645,74 +642,87 @@ void UserInputMapper::runMappings() {
}
// Now process the current values for each level of the stack
for (auto& mapping : _activeMappings) {
for (const auto& route : mapping->routes) {
if (route->conditional) {
if (!route->conditional->satisfied()) {
continue;
}
}
for (const auto& route : _deviceRoutes) {
if (!route) {
continue;
}
applyRoute(route);
}
auto source = route->source;
if (_overrides.count(source)) {
source = _overrides[source];
}
for (const auto& route : _standardRoutes) {
if (!route) {
continue;
}
applyRoute(route);
}
// Endpoints can only be read once (though a given mapping can route them to
// multiple places). Consider... If the default is to wire the A button to JUMP
// and someone else wires it to CONTEXT_MENU, I don't want both to occur when
// I press the button. The exception is if I'm wiring a control back to itself
// in order to adjust my interface, like inverting the Y axis on an analog stick
if (!source->readable()) {
continue;
}
}
auto input = source->getInput();
float value = source->value();
if (value != 0.0) {
int i = 0;
}
auto destination = route->destination;
// THis could happen if the route destination failed to create
// FIXME: Maybe do not create the route if the destination failed and avoid this case ?
if (!destination) {
continue;
}
// FIXME?, should come before or after the override logic?
if (!destination->writeable()) {
continue;
}
// Only consume the input if the route isn't a loopback.
// This allows mappings like `mapping.from(xbox.RY).invert().to(xbox.RY);`
bool loopback = (source->getInput() == destination->getInput()) && (source->getInput() != Input::INVALID_INPUT);
// Each time we loop back we re-write the override
if (loopback) {
_overrides[source] = destination = std::make_shared<StandardEndpoint>(source->getInput());
}
// Fetch the value, may have been overriden by previous loopback routes
if (source->isPose()) {
Pose value = getPose(source);
// no filters yet for pose
destination->apply(value, Pose(), source);
} else {
// Fetch the value, may have been overriden by previous loopback routes
float value = getValue(source);
// Apply each of the filters.
for (const auto& filter : route->filters) {
value = filter->apply(value);
}
destination->apply(value, 0, source);
}
void UserInputMapper::applyRoute(const Route::Pointer& route) {
if (route->conditional) {
if (!route->conditional->satisfied()) {
return;
}
}
auto source = route->source;
if (_overrides.count(source)) {
source = _overrides[source];
}
// Endpoints can only be read once (though a given mapping can route them to
// multiple places). Consider... If the default is to wire the A button to JUMP
// and someone else wires it to CONTEXT_MENU, I don't want both to occur when
// I press the button. The exception is if I'm wiring a control back to itself
// in order to adjust my interface, like inverting the Y axis on an analog stick
if (!source->readable()) {
return;
}
auto input = source->getInput();
float value = source->value();
if (value != 0.0) {
int i = 0;
}
auto destination = route->destination;
// THis could happen if the route destination failed to create
// FIXME: Maybe do not create the route if the destination failed and avoid this case ?
if (!destination) {
return;
}
// FIXME?, should come before or after the override logic?
if (!destination->writeable()) {
return;
}
// Only consume the input if the route isn't a loopback.
// This allows mappings like `mapping.from(xbox.RY).invert().to(xbox.RY);`
bool loopback = (source->getInput() == destination->getInput()) && (source->getInput() != Input::INVALID_INPUT);
// Each time we loop back we re-write the override
if (loopback) {
_overrides[source] = destination = std::make_shared<StandardEndpoint>(source->getInput());
}
// Fetch the value, may have been overriden by previous loopback routes
if (source->isPose()) {
Pose value = getPose(source);
// no filters yet for pose
destination->apply(value, Pose(), source);
} else {
// Fetch the value, may have been overriden by previous loopback routes
float value = getValue(source);
// Apply each of the filters.
for (const auto& filter : route->filters) {
value = filter->apply(value);
}
destination->apply(value, 0, source);
}
}
Endpoint::Pointer UserInputMapper::endpointFor(const QJSValue& endpoint) {
@ -744,6 +754,7 @@ Endpoint::Pointer UserInputMapper::endpointFor(const QScriptValue& endpoint) {
}
Endpoint::Pointer UserInputMapper::endpointFor(const Input& inputId) const {
Locker locker(_lock);
auto iterator = _endpointsByInput.find(inputId);
if (_endpointsByInput.end() == iterator) {
qWarning() << "Unknown input: " << QString::number(inputId.getID(), 16);
@ -767,6 +778,7 @@ Endpoint::Pointer UserInputMapper::compositeEndpointFor(Endpoint::Pointer first,
Mapping::Pointer UserInputMapper::newMapping(const QString& mappingName) {
Locker locker(_lock);
if (_mappingsByName.count(mappingName)) {
qCWarning(controllers) << "Refusing to recreate mapping named " << mappingName;
}
@ -799,8 +811,8 @@ Mapping::Pointer UserInputMapper::newMapping(const QString& mappingName) {
// return result;
//}
void UserInputMapper::enableMapping(const QString& mappingName, bool enable) {
Locker locker(_lock);
qCDebug(controllers) << "Attempting to enable mapping " << mappingName;
auto iterator = _mappingsByName.find(mappingName);
if (_mappingsByName.end() == iterator) {
@ -810,18 +822,14 @@ void UserInputMapper::enableMapping(const QString& mappingName, bool enable) {
auto mapping = iterator->second;
if (enable) {
_activeMappings.push_front(mapping);
enableMapping(mapping);
} else {
auto activeIterator = std::find(_activeMappings.begin(), _activeMappings.end(), mapping);
if (_activeMappings.end() == activeIterator) {
qCWarning(controllers) << "Attempted to disable inactive mapping " << mappingName;
return;
}
_activeMappings.erase(activeIterator);
disableMapping(mapping);
}
}
float UserInputMapper::getValue(const Endpoint::Pointer& endpoint) const {
Locker locker(_lock);
auto valuesIterator = _overrides.find(endpoint);
if (_overrides.end() != valuesIterator) {
return valuesIterator->second->value();
@ -854,6 +862,7 @@ Pose UserInputMapper::getPose(const Input& input) const {
}
Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) {
Locker locker(_lock);
if (jsonFile.isEmpty()) {
return Mapping::Pointer();
}
@ -1102,6 +1111,36 @@ Mapping::Pointer UserInputMapper::parseMapping(const QString& json) {
return parseMapping(doc.object());
}
void UserInputMapper::enableMapping(const Mapping::Pointer& mapping) {
Locker locker(_lock);
// New routes for a device get injected IN FRONT of existing routes. Routes
// are processed in order so this ensures that the standard -> action processing
// takes place after all of the hardware -> standard or hardware -> action processing
// because standard -> action is the first set of routes added.
for (auto route : mapping->routes) {
if (route->source->getInput().device == STANDARD_DEVICE) {
_standardRoutes.push_front(route);
} else {
_deviceRoutes.push_front(route);
}
}
}
void UserInputMapper::disableMapping(const Mapping::Pointer& mapping) {
Locker locker(_lock);
const auto& deviceRoutes = mapping->routes;
std::set<Route::Pointer> routeSet(deviceRoutes.begin(), deviceRoutes.end());
// FIXME this seems to result in empty route pointers... need to find a better way to remove them.
std::remove_if(_deviceRoutes.begin(), _deviceRoutes.end(), [&](Route::Pointer route)->bool {
return routeSet.count(route) != 0;
});
std::remove_if(_standardRoutes.begin(), _standardRoutes.end(), [&](Route::Pointer route)->bool {
return routeSet.count(route) != 0;
});
}
}
#include "UserInputMapper.moc"

View file

@ -15,6 +15,7 @@
#include <unordered_set>
#include <functional>
#include <memory>
#include <mutex>
#include <QtQml/QJSValue>
#include <QtScript/QScriptValue>
@ -119,10 +120,8 @@ namespace controller {
void hardwareChanged();
protected:
virtual void runMappings();
// GetFreeDeviceID should be called before registering a device to use an ID not used by a different device.
uint16 getFreeDeviceID() { return _nextFreeDeviceID++; }
InputDevice::Pointer _standardController;
DevicesMap _registeredDevices;
uint16 _nextFreeDeviceID = STANDARD_DEVICE + 1;
@ -142,6 +141,11 @@ namespace controller {
friend class RouteBuilderProxy;
friend class MappingBuilderProxy;
void runMappings();
void applyRoute(const Route::Pointer& route);
void enableMapping(const Mapping::Pointer& mapping);
void disableMapping(const Mapping::Pointer& mapping);
Endpoint::Pointer endpointFor(const QJSValue& endpoint);
Endpoint::Pointer endpointFor(const QScriptValue& endpoint);
Endpoint::Pointer endpointFor(const Input& endpoint) const;
@ -163,9 +167,14 @@ namespace controller {
EndpointOverrideMap _overrides;
MappingNameMap _mappingsByName;
Mapping::Pointer _defaultMapping{ std::make_shared<Mapping>("Default") };
MappingDeviceMap _mappingsByDevice;
MappingStack _activeMappings;
Route::List _deviceRoutes;
Route::List _standardRoutes;
using Locker = std::unique_lock<std::recursive_mutex>;
mutable std::recursive_mutex _lock;
};
}