Wiring up step yaw

This commit is contained in:
Brad Davis 2015-10-21 18:40:13 -07:00
parent 81df30d9e5
commit 637654adea
8 changed files with 210 additions and 207 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,20 @@ 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);
testMapping.from(standard.RX).to(actions.StepYaw);
}
function toggleMapping() {
testMapping.enable(!testMappingEnabled);
testMappingEnabled = !testMappingEnabled;
}
Component.onCompleted: {
enabled = true
@ -49,110 +61,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 +90,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

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