From 12e103c90c03c707c9c13601166e06ee99d17fb2 Mon Sep 17 00:00:00 2001 From: samcake Date: Wed, 14 Oct 2015 17:41:39 -0700 Subject: [PATCH] Get json creating the mappings from js --- examples/controllers/controllerMappings.js | 71 ++++++++ .../controllers/src/controllers/Filter.cpp | 46 ++++- .../controllers/src/controllers/Filter.h | 157 +++++++++--------- .../NewControllerScriptingInterface.cpp | 35 +++- .../NewControllerScriptingInterface.h | 1 + .../controllers/impl/RouteBuilderProxy.cpp | 28 +--- .../src/input-plugins/UserInputMapper.cpp | 46 ++++- .../src/input-plugins/UserInputMapper.h | 5 +- 8 files changed, 275 insertions(+), 114 deletions(-) create mode 100644 examples/controllers/controllerMappings.js diff --git a/examples/controllers/controllerMappings.js b/examples/controllers/controllerMappings.js new file mode 100644 index 0000000000..45383886c2 --- /dev/null +++ b/examples/controllers/controllerMappings.js @@ -0,0 +1,71 @@ + +// +// controllerScriptingExamples.js +// examples +// +// Created by Sam Gondelman on 6/2/15 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// Assumes you only have the default keyboard connected + +myFirstMapping = function() { +return { + "name": "example mapping from Standard to actions", + "channels": [ { + "from": "Keyboard.A", + "filters": [ { + "type": "clamp", + "params": [0, 1], + } + ], + "to": "Actions.LONGITUDINAL_FORWARD", + }, { + "from": "Keyboard.Left", + "filters": [ { + "type": "clamp", + "params": [0, 1], + }, { + "type": "invert" + } + ], + "to": "Actions.LONGITUDINAL_BACKWARD", + }, { + "from": "Keyboard.C", + "filters": [ { + "type": "scale", + "params": [2.0], + } + ], + "to": "Actions.Yaw", + }, { + "from": "Keyboard.B", + "to": "Actions.Yaw" + } + ] +} +} + + +//Script.include('mapping-test0.json'); +var myFirstMappingJSON = myFirstMapping(); +print('myFirstMappingJSON' + JSON.stringify(myFirstMappingJSON)); + +var mapping = NewControllers.parseMapping(JSON.stringify(myFirstMappingJSON)); + +Object.keys(Controller.Standard).forEach(function (input) { + print("Controller.Standard." + input + ":" + Controller.Standard[input]); +}); + +Object.keys(Controller.Hardware).forEach(function (deviceName) { + Object.keys(Controller.Hardware[deviceName]).forEach(function (input) { + print("Controller.Hardware." + deviceName + "." + input + ":" + Controller.Hardware[deviceName][input]); + }); +}); + +Object.keys(Controller.Actions).forEach(function (actionName) { + print("Controller.Actions." + actionName + ":" + Controller.Actions[actionName]); +}); diff --git a/libraries/controllers/src/controllers/Filter.cpp b/libraries/controllers/src/controllers/Filter.cpp index b7175f1716..3e1079b984 100644 --- a/libraries/controllers/src/controllers/Filter.cpp +++ b/libraries/controllers/src/controllers/Filter.cpp @@ -44,7 +44,7 @@ Filter::Pointer Filter::parse(const QJsonObject& json) { return Filter::Pointer(); } -Filter::Factory::ClassEntry ScaleFilter::_factoryEntry; +ScaleFilter::FactoryEntryBuilder ScaleFilter::_factoryEntryBuilder; bool ScaleFilter::parseParameters(const QJsonArray& parameters) { if (parameters.size() > 1) { @@ -53,7 +53,39 @@ bool ScaleFilter::parseParameters(const QJsonArray& parameters) { return true; } -Filter::Factory::ClassEntry PulseFilter::_factoryEntry; +InvertFilter::FactoryEntryBuilder InvertFilter::_factoryEntryBuilder; + + +ClampFilter::FactoryEntryBuilder ClampFilter::_factoryEntryBuilder; + +bool ClampFilter::parseParameters(const QJsonArray& parameters) { + if (parameters.size() > 1) { + _min = parameters[0].toDouble(); + } + if (parameters.size() > 2) { + _max = parameters[1].toDouble(); + } + return true; +} + +DeadZoneFilter::FactoryEntryBuilder DeadZoneFilter::_factoryEntryBuilder; + +float DeadZoneFilter::apply(float value) const { + float scale = 1.0f / (1.0f - _min); + if (abs(value) < _min) { + return 0.0f; + } + return (value - _min) * scale; +} + +bool DeadZoneFilter::parseParameters(const QJsonArray& parameters) { + if (parameters.size() > 1) { + _min = parameters[0].toDouble(); + } + return true; +} + +PulseFilter::FactoryEntryBuilder PulseFilter::_factoryEntryBuilder; float PulseFilter::apply(float value) const { @@ -72,5 +104,13 @@ float PulseFilter::apply(float value) const { } bool PulseFilter::parseParameters(const QJsonArray& parameters) { - return false; + if (parameters.size() > 1) { + _interval = parameters[0].toDouble(); + } + return true; } + +ConstrainToIntegerFilter::FactoryEntryBuilder ConstrainToIntegerFilter::_factoryEntryBuilder; + +ConstrainToPositiveIntegerFilter::FactoryEntryBuilder ConstrainToPositiveIntegerFilter::_factoryEntryBuilder; + diff --git a/libraries/controllers/src/controllers/Filter.h b/libraries/controllers/src/controllers/Filter.h index 4d21966731..4d8c483b08 100644 --- a/libraries/controllers/src/controllers/Filter.h +++ b/libraries/controllers/src/controllers/Filter.h @@ -16,6 +16,8 @@ #include #include +#include + #include class QJsonObject; @@ -23,40 +25,7 @@ class QJsonArray; namespace controller { - /* - template class Factory { - public: - template class Entry { - public: - virtual T* create() = 0; - }; - - template class DefaultEntry{ - public: - T* create() { return new S(); } - }; - - using EntryMap = std::map>>; - - void registerEntry(const std::string& name, std::unique_ptr>& entry) { - if (entry) { - _entries[name] = entry; - } - } - - T* create(const std::string& name) const { - auto& entryIt = _entries.find(name); - if (entryIt != _entries.end()) { - return (*entryIt).second->create(); - } - return nullptr; - } - - protected: - EntryMap _entries; - }; - */ // Encapsulates part of a filter chain class Filter { public: @@ -67,36 +36,40 @@ namespace controller { using Lambda = std::function; // Factory features - virtual bool parseParameters(const QJsonArray& parameters) = 0; + virtual bool parseParameters(const QJsonArray& parameters) { return true; } class Factory { public: class Entry { public: - virtual Filter* create() = 0; - virtual const std::string& getName() const = 0; + virtual Filter* create() const = 0; + virtual const char* getName() const = 0; - Entry() = default; - virtual ~Entry() = default; + Entry() {}; + virtual ~Entry() {}; }; - template class ClassEntry { + template class ClassEntry : public Entry { public: - Filter* create() override { return (Filter*) new T(); } - const std::string& getName() const override { - return _name - }; + virtual Filter* create() const { return (Filter*) new T(); } + virtual const char* getName() const { return T::getName(); }; - ClassEntry() : _name(name) {}; + ClassEntry() {}; virtual ~ClassEntry() = default; - const std::string _name; + class Builder { + public: + Builder() { + std::shared_ptr classEntry(new ClassEntry()); + Filter::getFactory().registerEntry(classEntry); + } + }; }; using EntryMap = std::map>; - void registerEntry(const std::shared_ptr& entry) { + void registerEntry(std::shared_ptr& entry) { if (entry) { _entries.insert(EntryMap::value_type(entry->getName(), entry)); } @@ -122,8 +95,9 @@ namespace controller { } #define REGISTER_FILTER_CLASS(classEntry, className) \ - using FactoryEntry = Filter::Factory::ClassEntry;\ - static FactoryEntry _factoryEntry; + static const char* getName() { return className; } \ + using FactoryEntryBuilder = Filter::Factory::ClassEntry::Builder;\ + static FactoryEntryBuilder _factoryEntryBuilder; namespace controller { @@ -150,6 +124,7 @@ namespace controller { class ScaleFilter : public Filter { public: + REGISTER_FILTER_CLASS(ScaleFilter, "scale"); ScaleFilter() {} ScaleFilter(float scale): _scale(scale) {} @@ -158,38 +133,48 @@ namespace controller { } virtual bool parseParameters(const QJsonArray& parameters); - // static Filter::Factory::ClassEntry _factoryEntry; - REGISTER_FILTER_CLASS(ScaleFilter, "scale"); private: float _scale = 1.0f; }; - //class AbstractRangeFilter : public Filter { - //public: - // RangeFilter(float min, float max) : _min(min), _max(max) {} + class InvertFilter : public ScaleFilter { + public: + REGISTER_FILTER_CLASS(InvertFilter, "invert"); + InvertFilter() : ScaleFilter(-1.0f) {} + + virtual bool parseParameters(const QJsonArray& parameters) { return true; } - //protected: - // const float _min; - // const float _max; - //}; + private: + }; - ///* - //* Constrains will emit the input value on the first call, and every *interval* seconds, otherwise returns 0 - //*/ - //class PulseFilter : public Filter { - //public: - // PulseFilter(float interval); - // virtual float apply(float value) const override; + class ClampFilter : public Filter { + public: + REGISTER_FILTER_CLASS(ClampFilter, "clamp"); + ClampFilter(float min = 0.0, float max = 1.0) : _min(min), _max(max) {}; - //private: - // float _lastEmitTime{ -std::numeric_limits::max() }; - // const float _interval; - //}; + virtual float apply(float value) const override { + return glm::clamp(value, _min, _max); + } + virtual bool parseParameters(const QJsonArray& parameters) override; + protected: + float _min = 0.0f; + float _max = 1.0f; + }; + class DeadZoneFilter : public Filter { + public: + REGISTER_FILTER_CLASS(DeadZoneFilter, "deadZone"); + DeadZoneFilter(float min = 0.0) : _min(min) {}; + + virtual float apply(float value) const override; + virtual bool parseParameters(const QJsonArray& parameters) override; + protected: + float _min = 0.0f; + }; class PulseFilter : public Filter { public: - REGISTER_FILTER_CLASS(PulseFilter); + REGISTER_FILTER_CLASS(PulseFilter, "pulse"); PulseFilter() {} PulseFilter(float interval) : _interval(interval) {} @@ -199,15 +184,31 @@ namespace controller { virtual bool parseParameters(const QJsonArray& parameters); private: - mutable float _lastEmitTime{ -std::numeric_limits::max() }; + mutable float _lastEmitTime{ -::std::numeric_limits::max() }; float _interval = 1.0f; }; - ////class DeadzoneFilter : public AbstractRangeFilter { - ////public: - //// DeadzoneFilter(float min, float max = 1.0f); - //// virtual float apply(float newValue, float oldValue) override; - ////}; + class ConstrainToIntegerFilter : public Filter { + public: + REGISTER_FILTER_CLASS(ConstrainToIntegerFilter, "constrainToInteger"); + ConstrainToIntegerFilter() {}; + + virtual float apply(float value) const override { + return glm::sign(value); + } + protected: + }; + + class ConstrainToPositiveIntegerFilter : public Filter { + public: + REGISTER_FILTER_CLASS(ConstrainToPositiveIntegerFilter, "constrainToPositiveInteger"); + ConstrainToPositiveIntegerFilter() {}; + + virtual float apply(float value) const override { + return (value <= 0.0f) ? 0.0f : 1.0f; + } + protected: + }; //class EasingFilter : public Filter { //public: @@ -236,12 +237,6 @@ namespace controller { // const float _exponent; //}; - //class ClampFilter : public RangeFilter { - //public: - // ClampFilter(float min = 0.0, float max = 1.0) : RangeFilter(min, max) {}; - // virtual float apply(float value) const override; - //}; - //class AbsFilter : public Filter { //public: // virtual float apply(float value) const override; diff --git a/libraries/controllers/src/controllers/NewControllerScriptingInterface.cpp b/libraries/controllers/src/controllers/NewControllerScriptingInterface.cpp index c80e5fc721..baeae55128 100644 --- a/libraries/controllers/src/controllers/NewControllerScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/NewControllerScriptingInterface.cpp @@ -12,6 +12,9 @@ #include +#include +#include + #include #include #include @@ -22,8 +25,6 @@ #include "impl/MappingBuilderProxy.h" #include "Logging.h" -static const uint16_t ACTIONS_DEVICE = UserInputMapper::Input::INVALID_DEVICE - (uint16_t)1; - namespace controller { @@ -161,7 +162,7 @@ namespace controller { int actionNumber = 0; qCDebug(controllers) << "Setting up standard actions"; for (const auto& actionName : actionNames) { - UserInputMapper::Input actionInput(ACTIONS_DEVICE, actionNumber++, UserInputMapper::ChannelType::AXIS); + UserInputMapper::Input actionInput(UserInputMapper::Input::ACTIONS_DEVICE, actionNumber++, UserInputMapper::ChannelType::AXIS); qCDebug(controllers) << "\tAction: " << actionName << " " << QString::number(actionInput.getID(), 16); // Expose the IDs to JS _actions.insert(sanatizeName(actionName), actionInput.getID()); @@ -182,6 +183,34 @@ namespace controller { return new MappingBuilderProxy(*this, mapping); } + QObject* NewControllerScriptingInterface::parseMapping(const QString& json) { + + QJsonObject obj; + QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8()); + // check validity of the document + if (!doc.isNull()) { + if (doc.isObject()) { + obj = doc.object(); + + auto mapping = std::make_shared("default"); + auto mappingBuilder = new MappingBuilderProxy(*this, mapping); + + mappingBuilder->parse(obj); + + _mappingsByName[mapping->_name] = mapping; + + } else { + qDebug() << "Mapping json Document is not an object" << endl; + } + } else { + qDebug() << "Invalid JSON...\n" << json << endl; + } + + return nullptr; + } + + Q_INVOKABLE QObject* newMapping(const QJsonObject& json); + void NewControllerScriptingInterface::enableMapping(const QString& mappingName, bool enable) { auto iterator = _mappingsByName.find(mappingName); if (_mappingsByName.end() == iterator) { diff --git a/libraries/controllers/src/controllers/NewControllerScriptingInterface.h b/libraries/controllers/src/controllers/NewControllerScriptingInterface.h index bdc291bc03..784b8cf5bd 100644 --- a/libraries/controllers/src/controllers/NewControllerScriptingInterface.h +++ b/libraries/controllers/src/controllers/NewControllerScriptingInterface.h @@ -40,6 +40,7 @@ namespace controller { Q_INVOKABLE void update(); Q_INVOKABLE QObject* newMapping(const QString& mappingName = QUuid::createUuid().toString()); + Q_INVOKABLE QObject* parseMapping(const QString& json); Q_INVOKABLE void enableMapping(const QString& mappingName, bool enable = true); Q_INVOKABLE void disableMapping(const QString& mappingName) { enableMapping(mappingName, false); diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp index d606b52608..38efcfdb00 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -57,9 +57,7 @@ QObject* RouteBuilderProxy::filter(const QScriptValue& expression) { QObject* RouteBuilderProxy::clamp(float min, float max) { - addFilter([=](float value) { - return glm::clamp(value, min, max); - }); + addFilter(Filter::Pointer(new ClampFilter(min, max))); return this; } @@ -69,40 +67,28 @@ QObject* RouteBuilderProxy::scale(float multiplier) { } QObject* RouteBuilderProxy::invert() { - return scale(-1.0f); + addFilter(Filter::Pointer(new InvertFilter())); + return this; } QObject* RouteBuilderProxy::deadZone(float min) { - assert(min < 1.0f); - float scale = 1.0f / (1.0f - min); - addFilter([=](float value) { - if (abs(value) < min) { - return 0.0f; - } - return (value - min) * scale; - }); - + addFilter(Filter::Pointer(new DeadZoneFilter(min))); return this; } QObject* RouteBuilderProxy::constrainToInteger() { - addFilter([=](float value) { - return glm::sign(value); - }); + addFilter(Filter::Pointer(new ConstrainToIntegerFilter())); return this; } QObject* RouteBuilderProxy::constrainToPositiveInteger() { - addFilter([=](float value) { - return (value <= 0.0f) ? 0.0f : 1.0f; - }); + addFilter(Filter::Pointer(new ConstrainToPositiveIntegerFilter())); return this; } QObject* RouteBuilderProxy::pulse(float interval) { - Filter::Pointer filter = std::make_shared(interval); - addFilter(filter); + addFilter(Filter::Pointer(new PulseFilter(interval))); return this; } diff --git a/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp b/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp index 325dc5dfe7..0f797e9ad0 100755 --- a/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp +++ b/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp @@ -12,10 +12,15 @@ #include "UserInputMapper.h" #include "StandardController.h" +#include +Q_DECLARE_LOGGING_CATEGORY(userInputMapper) +Q_LOGGING_CATEGORY(userInputMapper, "hifi.userInputMapper") + const UserInputMapper::Input UserInputMapper::Input::INVALID_INPUT = UserInputMapper::Input(UINT16_MAX); const uint16_t UserInputMapper::Input::INVALID_DEVICE = INVALID_INPUT.getDevice(); const uint16_t UserInputMapper::Input::INVALID_CHANNEL = INVALID_INPUT.getChannel(); -const uint16_t UserInputMapper::Input::INVALID_TYPE = (uint16_t)INVALID_INPUT.getType(); +const uint16_t UserInputMapper::Input::INVALID_TYPE = (uint16_t)INVALID_INPUT.getType(); +const uint16_t UserInputMapper::Input::ACTIONS_DEVICE = INVALID_DEVICE - (uint16)1; // Default contruct allocate the poutput size with the current hardcoded action channels UserInputMapper::UserInputMapper() { @@ -31,6 +36,7 @@ UserInputMapper::~UserInputMapper() { bool UserInputMapper::registerDevice(uint16 deviceID, const DeviceProxy::Pointer& proxy){ proxy->_name += " (" + QString::number(deviceID) + ")"; _registeredDevices[deviceID] = proxy; + qCDebug(userInputMapper) << "Registered input device <" << proxy->_name << "> deviceID = " << deviceID; return true; } @@ -65,12 +71,18 @@ void UserInputMapper::resetDevice(uint16 deviceID) { } int UserInputMapper::findDevice(QString name) const { + if (_standardDevice && (_standardDevice->getName() == name)) { + return getStandardDeviceID(); + } + for (auto device : _registeredDevices) { if (device.second->_name.split(" (")[0] == name) { return device.first; + } else if (device.second->_baseName == name) { + return device.first; } } - return 0; + return Input::INVALID_DEVICE; } QVector UserInputMapper::getDeviceNames() { @@ -94,10 +106,34 @@ UserInputMapper::Input UserInputMapper::findDeviceInput(const QString& inputName int deviceID = findDevice(deviceName); if (deviceID != Input::INVALID_DEVICE) { - // getAllInputsForDevice(deviceID); + const auto& deviceProxy = _registeredDevices.at(deviceID); + auto deviceInputs = deviceProxy->getAvailabeInputs(); + + for (auto input : deviceInputs) { + if (input.second == inputName) { + return input.first; + } + } + + qCDebug(userInputMapper) << "Couldn\'t find InputChannel named <" << inputName << "> for device <" << deviceName << ">"; + + } else if (deviceName == "Actions") { + deviceID = Input::ACTIONS_DEVICE; + int actionNum = 0; + for (auto action : _actionNames) { + if (action == inputName) { + return Input(Input::ACTIONS_DEVICE, actionNum, ChannelType::AXIS); + } + actionNum++; + } + + qCDebug(userInputMapper) << "Couldn\'t find ActionChannel named <" << inputName << "> among actions"; + + } else { + qCDebug(userInputMapper) << "Couldn\'t find InputDevice named <" << deviceName << ">"; } - - + } else { + qCDebug(userInputMapper) << "Couldn\'t understand <" << inputName << "> as a valid inputDevice.inputName"; } return Input(); diff --git a/libraries/input-plugins/src/input-plugins/UserInputMapper.h b/libraries/input-plugins/src/input-plugins/UserInputMapper.h index b7b105df5e..fae77cca92 100755 --- a/libraries/input-plugins/src/input-plugins/UserInputMapper.h +++ b/libraries/input-plugins/src/input-plugins/UserInputMapper.h @@ -85,6 +85,7 @@ public: static const uint16 INVALID_DEVICE; static const uint16 INVALID_CHANNEL; static const uint16 INVALID_TYPE; + static const uint16 ACTIONS_DEVICE; }; @@ -119,9 +120,11 @@ public: class DeviceProxy { public: - DeviceProxy(QString name) { _name = name; } + DeviceProxy(QString name) : _baseName(name), _name(name) {} + const QString& getBaseName() const { return _baseName; } const QString& getName() const { return _name; } + QString _baseName; QString _name; ButtonGetter getButton = [] (const Input& input, int timestamp) -> bool { return false; }; AxisGetter getAxis = [] (const Input& input, int timestamp) -> float { return 0.0f; };