From 637654adeacce63cdd0da4495ec5b609818c7a8f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 21 Oct 2015 18:40:13 -0700 Subject: [PATCH 1/3] Wiring up step yaw --- examples/tests/controllerInterfaceTest.js | 11 + interface/resources/qml/ScrollingGraph.qml | 12 +- interface/resources/qml/TestControllers.qml | 147 +++--------- .../resources/qml/controller/Standard.qml | 2 +- .../controllers/src/controllers/Actions.cpp | 7 +- .../controllers/src/controllers/Actions.h | 10 + .../src/controllers/UserInputMapper.cpp | 211 +++++++++++------- .../src/controllers/UserInputMapper.h | 17 +- 8 files changed, 210 insertions(+), 207 deletions(-) diff --git a/examples/tests/controllerInterfaceTest.js b/examples/tests/controllerInterfaceTest.js index fa8cf48b9b..48ad8f0879 100644 --- a/examples/tests/controllerInterfaceTest.js +++ b/examples/tests/controllerInterfaceTest.js @@ -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(); + } } diff --git a/interface/resources/qml/ScrollingGraph.qml b/interface/resources/qml/ScrollingGraph.qml index b5eaac6f89..26ca9a61ff 100644 --- a/interface/resources/qml/ScrollingGraph.qml +++ b/interface/resources/qml/ScrollingGraph.qml @@ -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() diff --git a/interface/resources/qml/TestControllers.qml b/interface/resources/qml/TestControllers.qml index a5deaed159..f1b8640c02 100644 --- a/interface/resources/qml/TestControllers.qml +++ b/interface/resources/qml/TestControllers.qml @@ -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 } } } diff --git a/interface/resources/qml/controller/Standard.qml b/interface/resources/qml/controller/Standard.qml index cee79fe50c..45e4febfa2 100644 --- a/interface/resources/qml/controller/Standard.qml +++ b/interface/resources/qml/controller/Standard.qml @@ -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 } } diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 7954ab5522..b0d2d24edf 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -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"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index 77a772de9e..47f04141f3 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -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, diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index d80952a5d9..ae806ed613 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -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(); 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 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 UserInputMapper::getDeviceNames() { + Locker locker(_lock); QVector 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 UserInputMapper::getAllActions() const { + Locker locker(_lock); QVector actions; for (auto i = 0; i < toInt(Action::NUM_ACTIONS); i++) { actions.append(Action(i)); @@ -518,6 +513,7 @@ QVector 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 UserInputMapper::getActionNames() const { + Locker locker(_lock); QVector 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(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(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 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" diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 345bba8c2b..70cd227e05 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -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("Default") }; MappingDeviceMap _mappingsByDevice; - MappingStack _activeMappings; + + Route::List _deviceRoutes; + Route::List _standardRoutes; + + using Locker = std::unique_lock; + + mutable std::recursive_mutex _lock; }; } From 044a28212d6b2430715c5d68243a6891b0a70b84 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 21 Oct 2015 20:44:38 -0700 Subject: [PATCH 2/3] Wiring step yaw to the avatar --- interface/resources/qml/TestControllers.qml | 3 +- interface/src/Application.cpp | 20 ++-- interface/src/avatar/Avatar.h | 15 --- interface/src/avatar/MyAvatar.cpp | 120 +++++++++----------- interface/src/avatar/MyAvatar.h | 16 +++ 5 files changed, 78 insertions(+), 96 deletions(-) diff --git a/interface/resources/qml/TestControllers.qml b/interface/resources/qml/TestControllers.qml index f1b8640c02..54b3cbf655 100644 --- a/interface/resources/qml/TestControllers.qml +++ b/interface/resources/qml/TestControllers.qml @@ -27,7 +27,8 @@ HifiControls.VrDialog { 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); + // Step yaw takes a number of degrees + testMapping.from(standard.RX).scale(15.0).to(actions.StepYaw); } function toggleMapping() { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4418f94b3a..3b54562770 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -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, 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); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 9a46a145c2..6a1f216089 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -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, diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5202138147..8ada0ca481 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -242,6 +242,7 @@ void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("transform"); updateOrientation(deltaTime); updatePosition(deltaTime); + _lastStepPulse = _thisStepPulse; } { @@ -1552,71 +1553,49 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const { !cameraInsideHead()); } +static quint64 COMFORT_MODE_PULSE_TIMING = USECS_PER_SECOND / 2; // turn once per half second + 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) { + _thisStepPulse = now; + 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,15 +1655,20 @@ 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]); - 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; + float stepControllerInput = fabsf(_driveKeys[STEP_TRANSLATE_Z]) + fabsf(_driveKeys[STEP_TRANSLATE_Z]) + fabsf(_driveKeys[STEP_TRANSLATE_Z]); + quint64 now = usecTimestampNow(); + if (stepControllerInput && now - _lastStepPulse > COMFORT_MODE_PULSE_TIMING) { + _thisStepPulse = now; + } + float keyboardInput = fabsf(_driveKeys[TRANSLATE_Z]) + fabsf(_driveKeys[TRANSLATE_X]) + fabsf(_driveKeys[TRANSLATE_Y]); + if (keyboardInput || (_thisStepPulse == now)) { + // Compute keyboard input + glm::vec3 front = (_driveKeys[TRANSLATE_Z]) * IDENTITY_FRONT; + glm::vec3 right = (_driveKeys[TRANSLATE_X]) * IDENTITY_RIGHT; + glm::vec3 up = (_driveKeys[TRANSLATE_Y]) * IDENTITY_UP; + + // FIXME how do I implement step translation as well? glm::vec3 direction = front + right + up; float directionLength = glm::length(direction); @@ -1734,7 +1718,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(_boomLength, ZOOM_MIN, ZOOM_MAX); @@ -1983,7 +1967,7 @@ void MyAvatar::clearDriveKeys() { } void MyAvatar::relayDriveKeysToCharacterController() { - if (_driveKeys[UP] > 0.0f) { + if (_driveKeys[TRANSLATE_Y] > 0.0f) { _characterController.jump(); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 4a0ae514f6..64814974b2 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -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; + quint64 _thisStepPulse = 0; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); From afcec347ffab06a3ac9ce7a5367d2393bdde93f9 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 21 Oct 2015 21:35:19 -0700 Subject: [PATCH 3/3] Wiring yaw action to avatar --- interface/resources/qml/TestControllers.qml | 4 ++- interface/src/Application.cpp | 2 +- interface/src/avatar/MyAvatar.cpp | 33 ++++++++++++++++----- interface/src/avatar/MyAvatar.h | 2 +- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/TestControllers.qml b/interface/resources/qml/TestControllers.qml index 54b3cbf655..3d1e13c6e3 100644 --- a/interface/resources/qml/TestControllers.qml +++ b/interface/resources/qml/TestControllers.qml @@ -26,8 +26,10 @@ HifiControls.VrDialog { function buildMapping() { testMapping = Controller.newMapping(); testMapping.from(standard.RY).invert().to(actions.Pitch); - testMapping.makeAxis(standard.LB, standard.RB).to(actions.Yaw); + //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); } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3b54562770..0a634425bc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2732,7 +2732,7 @@ 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(PITCH, userInputMapper->getActionState(controller::Action::PITCH) / 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); } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8ada0ca481..d822d37055 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -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,9 +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); - _lastStepPulse = _thisStepPulse; + + if (_pulseUpdate) { + _lastStepPulse = now; + _pulseUpdate = false; + } } { @@ -1553,8 +1576,6 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const { !cameraInsideHead()); } -static quint64 COMFORT_MODE_PULSE_TIMING = USECS_PER_SECOND / 2; // turn once per half second - void MyAvatar::updateOrientation(float deltaTime) { // Smoothly rotate body with arrow keys float targetSpeed = _driveKeys[YAW] * YAW_SPEED; @@ -1588,7 +1609,6 @@ void MyAvatar::updateOrientation(float deltaTime) { // snap turn every half second. quint64 now = usecTimestampNow(); if (_driveKeys[STEP_YAW] != 0.0f && now - _lastStepPulse > COMFORT_MODE_PULSE_TIMING) { - _thisStepPulse = now; totalBodyYaw += _driveKeys[STEP_YAW]; } @@ -1657,18 +1677,17 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe glm::vec3 newLocalVelocity = localVelocity; 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) { - _thisStepPulse = now; } float keyboardInput = fabsf(_driveKeys[TRANSLATE_Z]) + fabsf(_driveKeys[TRANSLATE_X]) + fabsf(_driveKeys[TRANSLATE_Y]); - if (keyboardInput || (_thisStepPulse == now)) { + if (keyboardInput) { // Compute keyboard input glm::vec3 front = (_driveKeys[TRANSLATE_Z]) * IDENTITY_FRONT; glm::vec3 right = (_driveKeys[TRANSLATE_X]) * IDENTITY_RIGHT; glm::vec3 up = (_driveKeys[TRANSLATE_Y]) * IDENTITY_UP; - // FIXME how do I implement step translation as well? glm::vec3 direction = front + right + up; float directionLength = glm::length(direction); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 64814974b2..c80a855149 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -391,7 +391,7 @@ private: glm::vec3 _lastPosition; bool _lastIsMoving = false; quint64 _lastStepPulse = 0; - quint64 _thisStepPulse = 0; + bool _pulseUpdate { false }; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);