From b9b03bd842e03a60ba447c4039c2583b08bc1e33 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 21 Oct 2015 14:31:22 -0700 Subject: [PATCH] Working on conditional and filter parsing --- interface/resources/controllers/hydra.json | 4 +- .../resources/controllers/standard-old.json | 43 ++ interface/resources/controllers/standard.json | 31 +- interface/resources/qml/TestControllers.qml | 53 +-- .../resources/qml/controller/AnalogButton.qml | 2 +- .../resources/qml/controller/AnalogStick.qml | 6 +- .../resources/qml/controller/ToggleButton.qml | 12 +- interface/resources/qml/controller/Xbox.qml | 15 + .../src/controllers/Conditional.cpp | 10 - .../controllers/src/controllers/Conditional.h | 10 - .../controllers/src/controllers/Endpoint.h | 25 +- libraries/controllers/src/controllers/Input.h | 1 + libraries/controllers/src/controllers/Pose.h | 1 + .../src/controllers/UserInputMapper.cpp | 429 +++++++++++++----- .../src/controllers/UserInputMapper.h | 22 +- 15 files changed, 453 insertions(+), 211 deletions(-) create mode 100644 interface/resources/controllers/standard-old.json diff --git a/interface/resources/controllers/hydra.json b/interface/resources/controllers/hydra.json index c20d54b7c1..20d954932a 100644 --- a/interface/resources/controllers/hydra.json +++ b/interface/resources/controllers/hydra.json @@ -1,10 +1,10 @@ { "name": "Hydra to Standard", "channels": [ - { "from": "Hydra.LY", "to": "Standard.LY" }, + { "from": "Hydra.LY", "filters": "invert", "to": "Standard.LY" }, { "from": "Hydra.LX", "to": "Standard.LX" }, { "from": "Hydra.LT", "to": "Standard.LT" }, - { "from": "Hydra.RY", "to": "Standard.RY" }, + { "from": "Hydra.RY", "filters": "invert", "to": "Standard.RY" }, { "from": "Hydra.RX", "to": "Standard.RX" }, { "from": "Hydra.RT", "to": "Standard.RT" }, diff --git a/interface/resources/controllers/standard-old.json b/interface/resources/controllers/standard-old.json new file mode 100644 index 0000000000..b662e5394d --- /dev/null +++ b/interface/resources/controllers/standard-old.json @@ -0,0 +1,43 @@ +{ + "name": "Standard to Action", + "channels": [ + { "from": "Standard.LY", "to": "Actions.TranslateZ" }, + { "from": "Standard.LX", "to": "Actions.TranslateX" }, + { "from": "Standard.RX", "to": "Actions.Yaw" }, + { "from": "Standard.RY", "to": "Actions.Pitch" }, + { + "from": "Standard.DU", + "to": "Actions.LONGITUDINAL_FORWARD", + "filters": [ { "type": "scale", "scale": 0.5 } ] + }, + { + "from": "Standard.DD", + "to": "Actions.LONGITUDINAL_BACKWARD", + "filters": [ { "type": "scale", "scale": 0.5 } ] + }, + { + "from": "Standard.DR", + "to": "Actions.LATERAL_RIGHT", + "filters": [ { "type": "scale", "scale": 0.5 } ] + }, + { + "from": "Standard.DL", + "to": "Actions.LATERAL_LEFT", + "filters": [ { "type": "scale", "scale": 0.5 } ] + }, + { "from": "Standard.Y", "to": "Actions.VERTICAL_UP" }, + { "from": "Standard.X", "to": "Actions.VERTICAL_DOWN" }, + { + "from": "Standard.RT", + "to": "Actions.BOOM_IN", + "filters": [ { "type": "scale", "scale": 0.1 } ] + }, + { + "from": "Standard.LT", + "to": "Actions.BOOM_OUT", + "filters": [ { "type": "scale", "scale": 0.1 } ] + }, + { "from": "Standard.LeftHand", "to": "Actions.LEFT_HAND" }, + { "from": "Standard.RightHand", "to": "Actions.RIGHT_HAND" } + ] +} diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index b662e5394d..ef6668ec07 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -5,38 +5,25 @@ { "from": "Standard.LX", "to": "Actions.TranslateX" }, { "from": "Standard.RX", "to": "Actions.Yaw" }, { "from": "Standard.RY", "to": "Actions.Pitch" }, - { - "from": "Standard.DU", - "to": "Actions.LONGITUDINAL_FORWARD", - "filters": [ { "type": "scale", "scale": 0.5 } ] - }, - { - "from": "Standard.DD", - "to": "Actions.LONGITUDINAL_BACKWARD", - "filters": [ { "type": "scale", "scale": 0.5 } ] - }, - { - "from": "Standard.DR", - "to": "Actions.LATERAL_RIGHT", - "filters": [ { "type": "scale", "scale": 0.5 } ] - }, - { - "from": "Standard.DL", - "to": "Actions.LATERAL_LEFT", - "filters": [ { "type": "scale", "scale": 0.5 } ] - }, - { "from": "Standard.Y", "to": "Actions.VERTICAL_UP" }, - { "from": "Standard.X", "to": "Actions.VERTICAL_DOWN" }, + + { "from": [ "Standard.DU", "Standard.DU", "Standard.DU", "Standard.DD" ], "to": "Standard.LeftPrimaryThumb" }, + { "from": "Standard.Back", "to": "Standard.LeftSecondaryThumb" }, + + { "from": [ "Standard.A", "Standard.B", "Standard.X", "Standard.Y" ], "to": "Standard.RightPrimaryThumb" }, + { "from": "Standard.Start", "to": "Standard.RightSecondaryThumb" }, + { "from": "Standard.RT", "to": "Actions.BOOM_IN", "filters": [ { "type": "scale", "scale": 0.1 } ] }, + { "from": "Standard.LT", "to": "Actions.BOOM_OUT", "filters": [ { "type": "scale", "scale": 0.1 } ] }, + { "from": "Standard.LeftHand", "to": "Actions.LEFT_HAND" }, { "from": "Standard.RightHand", "to": "Actions.RIGHT_HAND" } ] diff --git a/interface/resources/qml/TestControllers.qml b/interface/resources/qml/TestControllers.qml index e409b7a4a4..71a836f2e4 100644 --- a/interface/resources/qml/TestControllers.qml +++ b/interface/resources/qml/TestControllers.qml @@ -183,32 +183,33 @@ HifiControls.VrDialog { Xbox { device: root.xbox; label: "XBox"; width: 360 } Hydra { device: root.hydra; width: 360 } } -// Row { -// spacing: 8 -// ScrollingGraph { -// controlId: Controller.Actions.Yaw -// label: "Yaw" -// min: -3.0 -// max: 3.0 -// size: 128 -// } -// -// ScrollingGraph { -// controlId: Controller.Actions.YAW_LEFT -// label: "Yaw Left" -// min: -3.0 -// max: 3.0 -// size: 128 -// } -// -// ScrollingGraph { -// controlId: Controller.Actions.YAW_RIGHT -// label: "Yaw Right" -// min: -3.0 -// max: 3.0 -// size: 128 -// } -// } + + Row { + spacing: 8 + ScrollingGraph { + controlId: Controller.Actions.Yaw + label: "Yaw" + min: -3.0 + max: 3.0 + size: 128 + } + + ScrollingGraph { + controlId: Controller.Actions.YAW_LEFT + label: "Yaw Left" + min: -3.0 + max: 3.0 + size: 128 + } + + ScrollingGraph { + controlId: Controller.Actions.YAW_RIGHT + label: "Yaw Right" + min: -3.0 + max: 3.0 + size: 128 + } + } } } // dialog diff --git a/interface/resources/qml/controller/AnalogButton.qml b/interface/resources/qml/controller/AnalogButton.qml index d027332d42..82beb818ab 100644 --- a/interface/resources/qml/controller/AnalogButton.qml +++ b/interface/resources/qml/controller/AnalogButton.qml @@ -13,7 +13,7 @@ Item { property color color: 'black' function update() { - value = Controller.getValue(controlId); + value = controlId ? Controller.getValue(controlId) : 0; canvas.requestPaint(); } diff --git a/interface/resources/qml/controller/AnalogStick.qml b/interface/resources/qml/controller/AnalogStick.qml index 499e0e9ce9..c0d10bac59 100644 --- a/interface/resources/qml/controller/AnalogStick.qml +++ b/interface/resources/qml/controller/AnalogStick.qml @@ -17,8 +17,8 @@ Item { function update() { value = Qt.vector2d( - Controller.getValue(controlIds[0]), - Controller.getValue(controlIds[1]) + controlIds[0] ? Controller.getValue(controlIds[0]) : 0, + controlIds[1] ? Controller.getValue(controlIds[1]) : 0 ); if (root.invertY) { value.y = value.y * -1.0 @@ -27,7 +27,7 @@ Item { } Timer { - interval: 50; running: true; repeat: true + interval: 50; running: controlIds; repeat: true onTriggered: root.update() } diff --git a/interface/resources/qml/controller/ToggleButton.qml b/interface/resources/qml/controller/ToggleButton.qml index 6481045dd0..ee8bd380e2 100644 --- a/interface/resources/qml/controller/ToggleButton.qml +++ b/interface/resources/qml/controller/ToggleButton.qml @@ -12,12 +12,14 @@ Item { property real value: 0 property color color: 'black' + function update() { + value = controlId ? Controller.getValue(controlId) : 0; + canvas.requestPaint(); + } + Timer { - interval: 50; running: true; repeat: true - onTriggered: { - root.value = Controller.getValue(root.controlId); - canvas.requestPaint(); - } + interval: 50; running: root.controlId; repeat: true + onTriggered: root.update() } Canvas { diff --git a/interface/resources/qml/controller/Xbox.qml b/interface/resources/qml/controller/Xbox.qml index 4ff2959129..def2cf6fe8 100644 --- a/interface/resources/qml/controller/Xbox.qml +++ b/interface/resources/qml/controller/Xbox.qml @@ -100,5 +100,20 @@ Item { width: 16 * root.scale; height: 12 * root.scale x: (177 * root.scale); y: (45 * root.scale) } + + // Left primary + ToggleButton { + x: 0; y: parent.height - height; + controlId: root.device.LeftPrimaryThumb + width: 16 * root.scale; height: 16 * root.scale + } + + // Left primary + ToggleButton { + x: parent.width - width; y: parent.height - height; + controlId: root.device.RightPrimaryThumb + width: 16 * root.scale; height: 16 * root.scale + } + } } diff --git a/libraries/controllers/src/controllers/Conditional.cpp b/libraries/controllers/src/controllers/Conditional.cpp index 7173c80b76..00e42870e4 100644 --- a/libraries/controllers/src/controllers/Conditional.cpp +++ b/libraries/controllers/src/controllers/Conditional.cpp @@ -18,14 +18,4 @@ namespace controller { return Conditional::Pointer(); } - bool EndpointConditional::satisfied() { - if (!_endpoint) { - return false; - } - auto value = _endpoint->value(); - if (value == 0.0f) { - return false; - } - return true; - } } diff --git a/libraries/controllers/src/controllers/Conditional.h b/libraries/controllers/src/controllers/Conditional.h index d0226d5775..4d67d2871e 100644 --- a/libraries/controllers/src/controllers/Conditional.h +++ b/libraries/controllers/src/controllers/Conditional.h @@ -17,8 +17,6 @@ #include -#include "Endpoint.h" - class QJsonValue; namespace controller { @@ -41,14 +39,6 @@ namespace controller { static Factory _factory; }; - class EndpointConditional : public Conditional { - public: - EndpointConditional(Endpoint::Pointer endpoint) : _endpoint(endpoint) { } - virtual bool satisfied() override; - private: - Endpoint::Pointer _endpoint; - }; - } #define REGISTER_CONDITIONAL_CLASS(classEntry) \ diff --git a/libraries/controllers/src/controllers/Endpoint.h b/libraries/controllers/src/controllers/Endpoint.h index 5d529ace30..7a94b06e7e 100644 --- a/libraries/controllers/src/controllers/Endpoint.h +++ b/libraries/controllers/src/controllers/Endpoint.h @@ -40,9 +40,12 @@ namespace controller { virtual void apply(float newValue, float oldValue, const Pointer& source) = 0; virtual Pose pose() { return Pose(); } virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) {} - virtual const bool isPose() { return _input.isPose(); } + virtual bool writeable() const { return true; } + virtual bool readable() const { return true; } + virtual void reset() { } + const Input& getInput() { return _input; } protected: @@ -61,6 +64,26 @@ namespace controller { ReadLambda _readLambda; WriteLambda _writeLambda; }; + + + class VirtualEndpoint : public Endpoint { + public: + VirtualEndpoint(const Input& id = Input::INVALID_INPUT) + : Endpoint(id) { + } + + virtual float value() override { return _currentValue; } + virtual void apply(float newValue, float oldValue, const Pointer& source) override { _currentValue = newValue; } + + virtual Pose pose() override { return _currentPose; } + virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override { + _currentPose = newValue; + } + protected: + float _currentValue { 0.0f }; + Pose _currentPose {}; + }; + } #endif diff --git a/libraries/controllers/src/controllers/Input.h b/libraries/controllers/src/controllers/Input.h index 98377b7434..6f997c9f91 100644 --- a/libraries/controllers/src/controllers/Input.h +++ b/libraries/controllers/src/controllers/Input.h @@ -55,6 +55,7 @@ struct Input { Input(const Input& src) : id(src.id) {} Input& operator = (const Input& src) { id = src.id; return (*this); } bool operator ==(const Input& right) const { return INVALID_INPUT.id != id && INVALID_INPUT.id != right.id && id == right.id; } + bool operator !=(const Input& right) const { return !(*this == right); } bool operator < (const Input& src) const { return id < src.id; } static const Input INVALID_INPUT; diff --git a/libraries/controllers/src/controllers/Pose.h b/libraries/controllers/src/controllers/Pose.h index 9243ceb734..f0f9fbc012 100644 --- a/libraries/controllers/src/controllers/Pose.h +++ b/libraries/controllers/src/controllers/Pose.h @@ -30,6 +30,7 @@ namespace controller { Pose(const Pose&) = default; Pose& operator = (const Pose&) = default; bool operator ==(const Pose& right) const; + bool operator !=(const Pose& right) const { return !(*this == right); } bool isValid() const { return valid; } vec3 getTranslation() const { return translation; } quat getRotation() const { return rotation; } diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 6ef64fc784..ed688a47aa 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -55,22 +55,45 @@ private: float _lastValue = 0.0f; }; -class VirtualEndpoint : public Endpoint { +class StandardEndpoint : public VirtualEndpoint { public: - VirtualEndpoint(const Input& id = Input::INVALID_INPUT) - : Endpoint(id) { + StandardEndpoint(const Input& input) : VirtualEndpoint(input) {} + virtual bool writeable() const override { return !_written; } + virtual bool readable() const override { return !_read; } + virtual void reset() override { + apply(0.0f, 0.0f, Endpoint::Pointer()); + apply(Pose(), Pose(), Endpoint::Pointer()); + _written = _read = false; } - virtual float value() override { return _currentValue; } - virtual void apply(float newValue, float oldValue, const Pointer& source) override { _currentValue = newValue; } + virtual float value() override { + _read = true; + return VirtualEndpoint::value(); + } + + virtual void apply(float newValue, float oldValue, const Pointer& source) override { + // For standard endpoints, the first NON-ZERO write counts. + if (newValue != 0.0) { + _written = true; + } + VirtualEndpoint::apply(newValue, oldValue, source); + } + + virtual Pose pose() override { + _read = true; + return VirtualEndpoint::pose(); + } - virtual Pose pose() override { return _currentPose; } virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override { - _currentPose = newValue; + if (newValue != Pose()) { + _written = true; + } + VirtualEndpoint::apply(newValue, oldValue, source); } + private: - float _currentValue{ 0.0f }; - Pose _currentPose{}; + bool _written { false }; + bool _read { false }; }; @@ -136,12 +159,67 @@ public: virtual void apply(float newValue, float oldValue, const Pointer& source) { // Composites are read only } - -private: - Endpoint::Pointer _first; - Endpoint::Pointer _second; }; +class ArrayEndpoint : public Endpoint { + friend class UserInputMapper; +public: + using Pointer = std::shared_ptr; + ArrayEndpoint() : Endpoint(Input::INVALID_INPUT) { } + + virtual float value() override { + return 0.0; + } + + virtual void apply(float newValue, float oldValue, const Endpoint::Pointer& source) override { + for (auto& child : _children) { + if (child->writeable()) { + child->apply(newValue, oldValue, source); + } + } + } + + virtual bool readable() const override { return false; } + +private: + Endpoint::List _children; +}; + +class AnyEndpoint : public Endpoint { + friend class UserInputMapper; +public: + using Pointer = std::shared_ptr; + AnyEndpoint() : Endpoint(Input::INVALID_INPUT) {} + + virtual float value() override { + float result = 0; + for (auto& child : _children) { + float childResult = child->value(); + if (childResult != 0.0f) { + result = childResult; + } + } + return result; + } + + virtual void apply(float newValue, float oldValue, const Endpoint::Pointer& source) override { + qFatal("AnyEndpoint is read only"); + } + + virtual bool writeable() const override { return false; } + + virtual bool readable() const override { + for (auto& child : _children) { + if (!child->readable()) { + return false; + } + } + return true; + } + +private: + Endpoint::List _children; +}; class InputEndpoint : public Endpoint { public: @@ -150,40 +228,44 @@ public: } virtual float value() override { - _currentValue = 0.0f; + _read = true; if (isPose()) { - return _currentValue; + return pose().valid ? 1.0f : 0.0f; } auto userInputMapper = DependencyManager::get(); auto deviceProxy = userInputMapper->getDeviceProxy(_input); if (!deviceProxy) { - return _currentValue; + return 0.0f; } - _currentValue = deviceProxy->getValue(_input, 0); - return _currentValue; + return deviceProxy->getValue(_input, 0); } + + // FIXME need support for writing back to vibration / force feedback effects virtual void apply(float newValue, float oldValue, const Pointer& source) override {} virtual Pose pose() override { - _currentPose = Pose(); + _read = true; if (!isPose()) { - return _currentPose; + return Pose(); } auto userInputMapper = DependencyManager::get(); auto deviceProxy = userInputMapper->getDeviceProxy(_input); if (!deviceProxy) { - return _currentPose; + return Pose(); } - _currentPose = deviceProxy->getPose(_input, 0); - return _currentPose; + return deviceProxy->getPose(_input, 0); } - virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override { - } + virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override { } + + virtual bool writeable() const { return !_written; } + virtual bool readable() const { return !_read; } + virtual void reset() { _written = _read = false; } private: - float _currentValue{ 0.0f }; - Pose _currentPose{}; + + bool _written { false }; + bool _read { false }; }; class ActionEndpoint : public Endpoint { @@ -194,9 +276,8 @@ public: virtual float value() override { return _currentValue; } virtual void apply(float newValue, float oldValue, const Pointer& source) override { - _currentValue += newValue; - if (!(_input == Input::INVALID_INPUT)) { + if (_input != Input::INVALID_INPUT) { auto userInputMapper = DependencyManager::get(); userInputMapper->deltaActionState(Action(_input.getChannel()), newValue); } @@ -208,12 +289,17 @@ public: if (!_currentPose.isValid()) { return; } - if (!(_input == Input::INVALID_INPUT)) { + if (_input != Input::INVALID_INPUT) { auto userInputMapper = DependencyManager::get(); userInputMapper->setActionState(Action(_input.getChannel()), _currentPose); } } + virtual void reset() override { + _currentValue = 0.0f; + _currentPose = Pose(); + } + private: float _currentValue{ 0.0f }; Pose _currentPose{}; @@ -254,7 +340,7 @@ void UserInputMapper::registerDevice(InputDevice* device) { } Endpoint::Pointer endpoint; if (input.device == STANDARD_DEVICE) { - endpoint = std::make_shared(input); + endpoint = std::make_shared(input); } else if (input.device == ACTIONS_DEVICE) { endpoint = std::make_shared(input); } else { @@ -396,20 +482,7 @@ void UserInputMapper::update(float deltaTime) { } // Run the mappings code - update(); - - // Scale all the channel step with the scale - for (auto i = 0; i < toInt(Action::NUM_ACTIONS); i++) { - if (_externalActionStates[i] != 0) { - _actionStates[i] += _externalActionStates[i]; - _externalActionStates[i] = 0.0f; - } - - if (_externalPoseStates[i].isValid()) { - _poseStates[i] = _externalPoseStates[i]; - _externalPoseStates[i] = Pose(); - } - } + runMappings(); // merge the bisected and non-bisected axes for now fixBisectedAxis(_actionStates[toInt(Action::TRANSLATE_X)], _actionStates[toInt(Action::LATERAL_LEFT)], _actionStates[toInt(Action::LATERAL_RIGHT)]); @@ -419,7 +492,6 @@ void UserInputMapper::update(float deltaTime) { fixBisectedAxis(_actionStates[toInt(Action::ROTATE_Y)], _actionStates[toInt(Action::YAW_LEFT)], _actionStates[toInt(Action::YAW_RIGHT)]); fixBisectedAxis(_actionStates[toInt(Action::ROTATE_X)], _actionStates[toInt(Action::PITCH_UP)], _actionStates[toInt(Action::PITCH_DOWN)]); - static const float EPSILON = 0.01f; for (auto i = 0; i < toInt(Action::NUM_ACTIONS); i++) { _actionStates[i] *= _actionScales[i]; @@ -561,53 +633,62 @@ Input UserInputMapper::makeStandardInput(controller::StandardPoseChannel pose) { return Input(STANDARD_DEVICE, pose, ChannelType::POSE); } -void UserInputMapper::update() { +void UserInputMapper::runMappings() { static auto deviceNames = getDeviceNames(); - _overrideValues.clear(); + _overrides.clear(); - EndpointSet readEndpoints; - EndpointSet writtenEndpoints; + for (auto endpointEntry : this->_endpointsByInput) { + endpointEntry.second->reset(); + } // Now process the current values for each level of the stack for (auto& mapping : _activeMappings) { for (const auto& route : mapping->routes) { - const auto& source = route->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 (readEndpoints.count(source)) { - continue; - } - - const 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; - } - - if (writtenEndpoints.count(destination)) { - continue; - } - if (route->conditional) { if (!route->conditional->satisfied()) { continue; } } - // Standard controller destinations can only be can only be used once. - if (getStandardDeviceID() == destination->getInput().getDevice()) { - writtenEndpoints.insert(destination); + 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()) { + 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 == destination; - if (!loopback) { - readEndpoints.insert(source); + 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 @@ -624,14 +705,11 @@ void UserInputMapper::update() { value = filter->apply(value); } - if (loopback) { - _overrideValues[source] = value; - } else { - destination->apply(value, 0, source); - } + destination->apply(value, 0, source); } } } + } Endpoint::Pointer UserInputMapper::endpointFor(const QJSValue& endpoint) { @@ -741,9 +819,9 @@ void UserInputMapper::enableMapping(const QString& mappingName, bool enable) { } float UserInputMapper::getValue(const Endpoint::Pointer& endpoint) const { - auto valuesIterator = _overrideValues.find(endpoint); - if (_overrideValues.end() != valuesIterator) { - return valuesIterator->second; + auto valuesIterator = _overrides.find(endpoint); + if (_overrides.end() != valuesIterator) { + return valuesIterator->second->value(); } return endpoint->value(); @@ -787,27 +865,70 @@ Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) { return parseMapping(json); } - -const QString JSON_NAME = QStringLiteral("name"); -const QString JSON_CHANNELS = QStringLiteral("channels"); -const QString JSON_CHANNEL_FROM = QStringLiteral("from"); -const QString JSON_CHANNEL_WHEN = QStringLiteral("when"); -const QString JSON_CHANNEL_TO = QStringLiteral("to"); -const QString JSON_CHANNEL_FILTERS = QStringLiteral("filters"); +static const QString JSON_NAME = QStringLiteral("name"); +static const QString JSON_CHANNELS = QStringLiteral("channels"); +static const QString JSON_CHANNEL_FROM = QStringLiteral("from"); +static const QString JSON_CHANNEL_WHEN = QStringLiteral("when"); +static const QString JSON_CHANNEL_TO = QStringLiteral("to"); +static const QString JSON_CHANNEL_FILTERS = QStringLiteral("filters"); Endpoint::Pointer UserInputMapper::parseEndpoint(const QJsonValue& value) { + Endpoint::Pointer result; if (value.isString()) { auto input = findDeviceInput(value.toString()); - return endpointFor(input); + result = endpointFor(input); } else if (value.isObject()) { // Endpoint is defined as an object, we expect a js function then return Endpoint::Pointer(); } - return Endpoint::Pointer(); + + if (!result) { + qWarning() << "Invalid endpoint definition " << value; + } + return result; } +class AndConditional : public Conditional { +public: + using Pointer = std::shared_ptr; + + AndConditional(Conditional::List children) : _children(children) { } + + virtual bool satisfied() override { + for (auto& conditional : _children) { + if (!conditional->satisfied()) { + return false; + } + } + return true; + } + +private: + Conditional::List _children; +}; + +class EndpointConditional : public Conditional { +public: + EndpointConditional(Endpoint::Pointer endpoint) : _endpoint(endpoint) {} + virtual bool satisfied() override { return _endpoint && _endpoint->value() != 0.0; } +private: + Endpoint::Pointer _endpoint; +}; + Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value) { - if (value.isString()) { + if (value.isArray()) { + // Support "when" : [ "GamePad.RB", "GamePad.LB" ] + Conditional::List children; + for (auto arrayItem : value.toArray()) { + Conditional::Pointer childConditional = parseConditional(arrayItem); + if (!childConditional) { + return Conditional::Pointer(); + } + children.push_back(childConditional); + } + return std::make_shared(children); + } else if (value.isString()) { + // Support "when" : "GamePad.RB" auto input = findDeviceInput(value.toString()); auto endpoint = endpointFor(input); if (!endpoint) { @@ -815,11 +936,85 @@ Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value) } return std::make_shared(endpoint); - } - + } + return Conditional::parse(value); } + +Filter::Pointer UserInputMapper::parseFilter(const QJsonValue& value) { + Filter::Pointer result; + if (value.isString()) { + result = Filter::getFactory().create(value.toString()); + } else if (value.isObject()) { + result = Filter::parse(value.toObject()); + } + + if (!result) { + qWarning() << "Invalid filter definition " << value; + } + + return result; +} + + +Filter::List UserInputMapper::parseFilters(const QJsonValue& value) { + if (value.isNull()) { + return Filter::List(); + } + + if (value.isArray()) { + Filter::List result; + auto filtersArray = value.toArray(); + for (auto filterValue : filtersArray) { + Filter::Pointer filter = parseFilter(filterValue); + if (!filter) { + return Filter::List(); + } + result.push_back(filter); + } + return result; + } + + Filter::Pointer filter = parseFilter(value); + if (!filter) { + return Filter::List(); + } + return Filter::List({ filter }); +} + +Endpoint::Pointer UserInputMapper::parseDestination(const QJsonValue& value) { + if (value.isArray()) { + ArrayEndpoint::Pointer result = std::make_shared(); + for (auto arrayItem : value.toArray()) { + Endpoint::Pointer destination = parseEndpoint(arrayItem); + if (!destination) { + return Endpoint::Pointer(); + } + result->_children.push_back(destination); + } + return result; + } + + return parseEndpoint(value); +} + +Endpoint::Pointer UserInputMapper::parseSource(const QJsonValue& value) { + if (value.isArray()) { + AnyEndpoint::Pointer result = std::make_shared(); + for (auto arrayItem : value.toArray()) { + Endpoint::Pointer destination = parseEndpoint(arrayItem); + if (!destination) { + return Endpoint::Pointer(); + } + result->_children.push_back(destination); + } + return result; + } + + return parseEndpoint(value); +} + Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) { if (!value.isObject()) { return Route::Pointer(); @@ -827,47 +1022,37 @@ Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) { const auto& obj = value.toObject(); Route::Pointer result = std::make_shared(); - result->source = parseEndpoint(obj[JSON_CHANNEL_FROM]); + result->source = parseSource(obj[JSON_CHANNEL_FROM]); if (!result->source) { qWarning() << "Invalid route source " << obj[JSON_CHANNEL_FROM]; return Route::Pointer(); } - result->destination = parseEndpoint(obj[JSON_CHANNEL_TO]); + + + result->destination = parseDestination(obj[JSON_CHANNEL_TO]); if (!result->destination) { qWarning() << "Invalid route destination " << obj[JSON_CHANNEL_TO]; return Route::Pointer(); } if (obj.contains(JSON_CHANNEL_WHEN)) { - auto when = parseConditional(obj[JSON_CHANNEL_WHEN]); - if (!when) { - qWarning() << "Invalid route conditional " << obj[JSON_CHANNEL_TO]; + auto conditionalsValue = obj[JSON_CHANNEL_WHEN]; + result->conditional = parseConditional(conditionalsValue); + if (!result->conditional) { + qWarning() << "Invalid route conditionals " << conditionalsValue; return Route::Pointer(); } - result->conditional = when; } - const auto& filtersValue = obj[JSON_CHANNEL_FILTERS]; - // FIXME support strings for filters with no parameters, both in the array and at the top level... - // i.e. - // { "from": "Standard.DU", "to" : "Actions.LONGITUDINAL_FORWARD", "filters" : "invert" }, - // and - // { "from": "Standard.DU", "to" : "Actions.LONGITUDINAL_FORWARD", "filters" : [ "invert", "constrainToInteger" ] }, - if (filtersValue.isArray()) { - auto filtersArray = filtersValue.toArray(); - for (auto filterValue : filtersArray) { - if (!filterValue.isObject()) { - qWarning() << "Invalid filter " << filterValue; - return Route::Pointer(); - } - Filter::Pointer filter = Filter::parse(filterValue.toObject()); - if (!filter) { - qWarning() << "Invalid filter " << filterValue; - return Route::Pointer(); - } - result->filters.push_back(filter); + if (obj.contains(JSON_CHANNEL_FILTERS)) { + auto filtersValue = obj[JSON_CHANNEL_FILTERS]; + result->filters = parseFilters(filtersValue); + if (result->filters.empty()) { + qWarning() << "Invalid route filters " << filtersValue; + return Route::Pointer(); } } + return result; } diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index ec1267cd0c..345bba8c2b 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -50,7 +50,7 @@ namespace controller { using MappingStack = std::list; using InputToEndpointMap = std::map; using EndpointSet = std::unordered_set; - using ValueMap = std::map; + using EndpointOverrideMap = std::map; using EndpointPair = std::pair; using EndpointPairMap = std::map; using DevicesMap = std::map; @@ -86,9 +86,9 @@ namespace controller { int findAction(const QString& actionName) const; QVector getActionNames() const; - void setActionState(Action action, float value) { _externalActionStates[toInt(action)] = value; } - void deltaActionState(Action action, float delta) { _externalActionStates[toInt(action)] += delta; } - void setActionState(Action action, const Pose& value) { _externalPoseStates[toInt(action)] = value; } + void setActionState(Action action, float value) { _actionStates[toInt(action)] = value; } + void deltaActionState(Action action, float delta) { _actionStates[toInt(action)] += delta; } + void setActionState(Action action, const Pose& value) { _poseStates[toInt(action)] = value; } static Input makeStandardInput(controller::StandardButtonChannel button); static Input makeStandardInput(controller::StandardAxisChannel axis); @@ -119,7 +119,7 @@ namespace controller { void hardwareChanged(); protected: - virtual void update(); + 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++; } @@ -128,11 +128,9 @@ namespace controller { uint16 _nextFreeDeviceID = STANDARD_DEVICE + 1; std::vector _actionStates = std::vector(toInt(Action::NUM_ACTIONS), 0.0f); - std::vector _externalActionStates = std::vector(toInt(Action::NUM_ACTIONS), 0.0f); std::vector _actionScales = std::vector(toInt(Action::NUM_ACTIONS), 1.0f); std::vector _lastActionStates = std::vector(toInt(Action::NUM_ACTIONS), 0.0f); std::vector _poseStates = std::vector(toInt(Action::NUM_ACTIONS)); - std::vector _externalPoseStates = std::vector(toInt(Action::NUM_ACTIONS)); glm::mat4 _sensorToWorldMat; @@ -148,16 +146,22 @@ namespace controller { Endpoint::Pointer endpointFor(const QScriptValue& endpoint); Endpoint::Pointer endpointFor(const Input& endpoint) const; Endpoint::Pointer compositeEndpointFor(Endpoint::Pointer first, Endpoint::Pointer second); + Mapping::Pointer parseMapping(const QJsonValue& json); Route::Pointer parseRoute(const QJsonValue& value); - Conditional::Pointer parseConditional(const QJsonValue& value); + Endpoint::Pointer parseDestination(const QJsonValue& value); + Endpoint::Pointer parseSource(const QJsonValue& value); Endpoint::Pointer parseEndpoint(const QJsonValue& value); + Conditional::Pointer parseConditional(const QJsonValue& value); + + static Filter::Pointer parseFilter(const QJsonValue& value); + static Filter::List parseFilters(const QJsonValue& value); InputToEndpointMap _endpointsByInput; EndpointToInputMap _inputsByEndpoint; EndpointPairMap _compositeEndpoints; - ValueMap _overrideValues; + EndpointOverrideMap _overrides; MappingNameMap _mappingsByName; Mapping::Pointer _defaultMapping{ std::make_shared("Default") }; MappingDeviceMap _mappingsByDevice;