Merge pull request #6081 from samcake/controllers

Controllers mapping created from JSON, first draft
This commit is contained in:
Brad Hefta-Gaub 2015-10-15 15:19:42 -07:00
commit 1f6a2f3c21
14 changed files with 901 additions and 475 deletions

View file

@ -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 = Controller.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]);
});

View file

@ -11,11 +11,106 @@
#include <QtCore/QObject> #include <QtCore/QObject>
#include <QtScript/QScriptValue> #include <QtScript/QScriptValue>
namespace controller { #include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
Filter::Pointer Filter::parse(const QJsonObject& json) { #include "SharedUtil.h"
// FIXME parse the json object and determine the instance type to create
return Filter::Pointer(); using namespace controller;
const QString JSON_FILTER_TYPE = QStringLiteral("type");
const QString JSON_FILTER_PARAMS = QStringLiteral("params");
Filter::Factory Filter::_factory;
Filter::Pointer Filter::parse(const QJsonObject& json) {
// The filter is an object, now let s check for type and potential arguments
Filter::Pointer filter;
auto filterType = json[JSON_FILTER_TYPE];
if (filterType.isString()) {
filter.reset(Filter::getFactory().create(filterType.toString().toStdString()));
if (filter) {
// Filter is defined, need to read the parameters and validate
auto parameters = json[JSON_FILTER_PARAMS];
if (parameters.isArray()) {
if (filter->parseParameters(parameters.toArray())) {
}
}
return filter;
}
} }
return Filter::Pointer();
} }
ScaleFilter::FactoryEntryBuilder ScaleFilter::_factoryEntryBuilder;
bool ScaleFilter::parseParameters(const QJsonArray& parameters) {
if (parameters.size() > 1) {
_scale = parameters[0].toDouble();
}
return true;
}
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 {
float result = 0.0;
if (0.0 != value) {
float now = secTimestampNow();
float delta = now - _lastEmitTime;
if (delta >= _interval) {
_lastEmitTime = now;
result = value;
}
}
return result;
}
bool PulseFilter::parseParameters(const QJsonArray& parameters) {
if (parameters.size() > 1) {
_interval = parameters[0].toDouble();
}
return true;
}
ConstrainToIntegerFilter::FactoryEntryBuilder ConstrainToIntegerFilter::_factoryEntryBuilder;
ConstrainToPositiveIntegerFilter::FactoryEntryBuilder ConstrainToPositiveIntegerFilter::_factoryEntryBuilder;

View file

@ -14,10 +14,15 @@
#include <memory> #include <memory>
#include <numeric> #include <numeric>
#include <functional> #include <functional>
#include <map>
#include <GLMHelpers.h>
#include <QtCore/QEasingCurve> #include <QtCore/QEasingCurve>
class QJsonObject; class QJsonObject;
class QJsonArray;
namespace controller { namespace controller {
@ -30,18 +35,84 @@ namespace controller {
using List = std::list<Pointer>; using List = std::list<Pointer>;
using Lambda = std::function<float(float)>; using Lambda = std::function<float(float)>;
static Filter::Pointer parse(const QJsonObject& json); // Factory features
}; virtual bool parseParameters(const QJsonArray& parameters) { return true; }
class Factory {
public:
class Entry {
public:
virtual Filter* create() const = 0;
virtual const char* getName() const = 0;
Entry() {};
virtual ~Entry() {};
};
template <class T> class ClassEntry : public Entry {
public:
virtual Filter* create() const { return (Filter*) new T(); }
virtual const char* getName() const { return T::getName(); };
ClassEntry() {};
virtual ~ClassEntry() = default;
class Builder {
public:
Builder() {
std::shared_ptr<Entry> classEntry(new ClassEntry<T>());
Filter::getFactory().registerEntry(classEntry);
}
};
};
using EntryMap = std::map<std::string, std::shared_ptr<Entry>>;
void registerEntry(std::shared_ptr<Entry>& entry) {
if (entry) {
_entries.insert(EntryMap::value_type(entry->getName(), entry));
}
}
Filter* create(const std::string& name) const {
const auto& entryIt = _entries.find(name);
if (entryIt != _entries.end()) {
return (*entryIt).second->create();
}
return nullptr;
}
protected:
EntryMap _entries;
};
static Filter::Pointer parse(const QJsonObject& json);
static Factory& getFactory() { return _factory; }
protected:
static Factory _factory;
};
}
#define REGISTER_FILTER_CLASS(classEntry, className) \
static const char* getName() { return className; } \
using FactoryEntryBuilder = Filter::Factory::ClassEntry<classEntry>::Builder;\
static FactoryEntryBuilder _factoryEntryBuilder;
namespace controller {
class LambdaFilter : public Filter { class LambdaFilter : public Filter {
public: public:
// LambdaFilter() {}
LambdaFilter(Lambda f) : _function(f) {}; LambdaFilter(Lambda f) : _function(f) {};
virtual float apply(float value) const { virtual float apply(float value) const {
return _function(value); return _function(value);
} }
virtual bool parseParameters(const QJsonArray& parameters) { return true; }
// REGISTER_FILTER_CLASS(LambdaFilter);
private: private:
Lambda _function; Lambda _function;
}; };
@ -50,45 +121,94 @@ namespace controller {
public: public:
}; };
class ScaleFilter : public Filter {
public:
REGISTER_FILTER_CLASS(ScaleFilter, "scale");
ScaleFilter() {}
ScaleFilter(float scale): _scale(scale) {}
//class ScaleFilter : public Filter { virtual float apply(float value) const override {
//public: return value * _scale;
// ScaleFilter(float scale); }
// virtual float apply(float scale) const override { virtual bool parseParameters(const QJsonArray& parameters);
// return value * _scale;
// }
//private: private:
// const float _scale; float _scale = 1.0f;
//}; };
//class AbstractRangeFilter : public Filter { class InvertFilter : public ScaleFilter {
//public: public:
// RangeFilter(float min, float max) : _min(min), _max(max) {} REGISTER_FILTER_CLASS(InvertFilter, "invert");
InvertFilter() : ScaleFilter(-1.0f) {}
virtual bool parseParameters(const QJsonArray& parameters) { return true; }
//protected: private:
// const float _min; };
// const float _max;
//};
///* class ClampFilter : public Filter {
//* Constrains will emit the input value on the first call, and every *interval* seconds, otherwise returns 0 public:
//*/ REGISTER_FILTER_CLASS(ClampFilter, "clamp");
//class PulseFilter : public Filter { ClampFilter(float min = 0.0, float max = 1.0) : _min(min), _max(max) {};
//public:
// PulseFilter(float interval);
// virtual float apply(float value) const override;
//private: virtual float apply(float value) const override {
// float _lastEmitTime{ -std::numeric_limits<float>::max() }; return glm::clamp(value, _min, _max);
// const float _interval; }
//}; virtual bool parseParameters(const QJsonArray& parameters) override;
protected:
float _min = 0.0f;
float _max = 1.0f;
};
////class DeadzoneFilter : public AbstractRangeFilter { class DeadZoneFilter : public Filter {
////public: public:
//// DeadzoneFilter(float min, float max = 1.0f); REGISTER_FILTER_CLASS(DeadZoneFilter, "deadZone");
//// virtual float apply(float newValue, float oldValue) override; 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, "pulse");
PulseFilter() {}
PulseFilter(float interval) : _interval(interval) {}
virtual float apply(float value) const override;
virtual bool parseParameters(const QJsonArray& parameters);
private:
mutable float _lastEmitTime{ -::std::numeric_limits<float>::max() };
float _interval = 1.0f;
};
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 { //class EasingFilter : public Filter {
//public: //public:
@ -117,12 +237,6 @@ namespace controller {
// const float _exponent; // 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 { //class AbsFilter : public Filter {
//public: //public:
// virtual float apply(float value) const override; // virtual float apply(float value) const override;

View file

@ -1,3 +1,10 @@
//
// Created by Bradley Austin Davis 2015/10/09
// 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
//
#include "Mapping.h" #include "Mapping.h"
namespace controller { namespace controller {

View file

@ -30,10 +30,10 @@ namespace controller {
Mapping(const QString& name); Mapping(const QString& name);
Map _channelMappings; Map _channelMappings;
const QString _name;
void parse(const QString& json); QString _name;
QString serialize();
protected:
}; };
} }

View file

@ -12,6 +12,9 @@
#include <QtCore/QRegularExpression> #include <QtCore/QRegularExpression>
#include <QJsonDocument>
#include <QJsonObject>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <DependencyManager.h> #include <DependencyManager.h>
@ -19,8 +22,6 @@
#include "Logging.h" #include "Logging.h"
#include "InputDevice.h" #include "InputDevice.h"
static const uint16_t ACTIONS_DEVICE = UserInputMapper::Input::INVALID_DEVICE - (uint16_t)1;
namespace controller { namespace controller {
class VirtualEndpoint : public Endpoint { class VirtualEndpoint : public Endpoint {
@ -154,7 +155,7 @@ namespace controller {
int actionNumber = 0; int actionNumber = 0;
qCDebug(controllers) << "Setting up standard actions"; qCDebug(controllers) << "Setting up standard actions";
for (const auto& actionName : actionNames) { 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); qCDebug(controllers) << "\tAction: " << actionName << " " << QString::number(actionInput.getID(), 16);
// Expose the IDs to JS // Expose the IDs to JS
QString cleanActionName = QString(actionName).remove(ScriptingInterface::SANITIZE_NAME_EXPRESSION); QString cleanActionName = QString(actionName).remove(ScriptingInterface::SANITIZE_NAME_EXPRESSION);
@ -176,6 +177,34 @@ namespace controller {
return new MappingBuilderProxy(*this, mapping); return new MappingBuilderProxy(*this, mapping);
} }
QObject* ScriptingInterface::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<Mapping>("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 ScriptingInterface::enableMapping(const QString& mappingName, bool enable) { void ScriptingInterface::enableMapping(const QString& mappingName, bool enable) {
auto iterator = _mappingsByName.find(mappingName); auto iterator = _mappingsByName.find(mappingName);
if (_mappingsByName.end() == iterator) { if (_mappingsByName.end() == iterator) {
@ -318,6 +347,10 @@ namespace controller {
return Endpoint::Pointer(); return Endpoint::Pointer();
} }
UserInputMapper::Input ScriptingInterface::inputFor(const QString& inputName) {
return DependencyManager::get<UserInputMapper>()->findDeviceInput(inputName);
}
Endpoint::Pointer ScriptingInterface::endpointFor(const UserInputMapper::Input& inputId) { Endpoint::Pointer ScriptingInterface::endpointFor(const UserInputMapper::Input& inputId) {
auto iterator = _endpoints.find(inputId); auto iterator = _endpoints.find(inputId);
if (_endpoints.end() == iterator) { if (_endpoints.end() == iterator) {

View file

@ -72,6 +72,7 @@ namespace controller {
Q_INVOKABLE void disableMapping(const QString& mappingName) { Q_INVOKABLE void disableMapping(const QString& mappingName) {
enableMapping(mappingName, false); enableMapping(mappingName, false);
} }
Q_INVOKABLE QObject* parseMapping(const QString& json);
Q_INVOKABLE bool isPrimaryButtonPressed() const; Q_INVOKABLE bool isPrimaryButtonPressed() const;
Q_INVOKABLE glm::vec2 getPrimaryJoystickPosition() const; Q_INVOKABLE glm::vec2 getPrimaryJoystickPosition() const;
@ -120,6 +121,8 @@ namespace controller {
Endpoint::Pointer endpointFor(const QScriptValue& endpoint); Endpoint::Pointer endpointFor(const QScriptValue& endpoint);
Endpoint::Pointer endpointFor(const UserInputMapper::Input& endpoint); Endpoint::Pointer endpointFor(const UserInputMapper::Input& endpoint);
Endpoint::Pointer compositeEndpointFor(Endpoint::Pointer first, Endpoint::Pointer second); Endpoint::Pointer compositeEndpointFor(Endpoint::Pointer first, Endpoint::Pointer second);
UserInputMapper::Input inputFor(const QString& inputName);
QVariantMap _hardware; QVariantMap _hardware;
QVariantMap _actions; QVariantMap _actions;

View file

@ -1,382 +1,424 @@
// //
// UserInputMapper.cpp // UserInputMapper.cpp
// input-plugins/src/input-plugins // input-plugins/src/input-plugins
// //
// Created by Sam Gateau on 4/27/15. // Created by Sam Gateau on 4/27/15.
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "UserInputMapper.h"
#include "StandardController.h"
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();
// Default contruct allocate the poutput size with the current hardcoded action channels
UserInputMapper::UserInputMapper() {
registerStandardDevice();
assignDefaulActionScales();
createActionNames();
}
UserInputMapper::~UserInputMapper() {
}
int UserInputMapper::recordDeviceOfType(const QString& deviceName) {
if (!_deviceCounts.contains(deviceName)) {
_deviceCounts[deviceName] = 0;
}
_deviceCounts[deviceName] += 1;
return _deviceCounts[deviceName]; #include "UserInputMapper.h"
#include "StandardController.h"
#include "Logging.h"
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::ACTIONS_DEVICE = INVALID_DEVICE - (uint16)1;
// Default contruct allocate the poutput size with the current hardcoded action channels
UserInputMapper::UserInputMapper() {
registerStandardDevice();
assignDefaulActionScales();
createActionNames();
} }
bool UserInputMapper::registerDevice(uint16 deviceID, const DeviceProxy::Pointer& proxy) { UserInputMapper::~UserInputMapper() {
int numberOfType = recordDeviceOfType(proxy->_name); }
if (numberOfType > 1) {
proxy->_name += QString::number(numberOfType);
}
bool UserInputMapper::registerDevice(uint16 deviceID, const DeviceProxy::Pointer& proxy){
proxy->_name += " (" + QString::number(deviceID) + ")";
_registeredDevices[deviceID] = proxy; _registeredDevices[deviceID] = proxy;
qCDebug(controllers) << "Registered input device <" << proxy->_name << "> deviceID = " << deviceID;
return true; return true;
} }
UserInputMapper::DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) {
auto device = _registeredDevices.find(input.getDevice());
if (device != _registeredDevices.end()) {
return (device->second);
} else {
return DeviceProxy::Pointer();
}
}
QString UserInputMapper::getDeviceName(uint16 deviceID) {
if (_registeredDevices.find(deviceID) != _registeredDevices.end()) {
return _registeredDevices[deviceID]->_name;
}
return QString("unknown");
}
void UserInputMapper::resetAllDeviceBindings() {
for (auto device : _registeredDevices) {
device.second->resetDeviceBindings();
}
}
void UserInputMapper::resetDevice(uint16 deviceID) {
auto device = _registeredDevices.find(deviceID);
if (device != _registeredDevices.end()) {
device->second->resetDeviceBindings();
}
}
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 Input::INVALID_DEVICE;
}
QVector<QString> UserInputMapper::getDeviceNames() {
QVector<QString> result;
for (auto device : _registeredDevices) {
QString deviceName = device.second->_name.split(" (")[0];
result << deviceName;
}
return result;
}
UserInputMapper::Input UserInputMapper::findDeviceInput(const QString& inputName) const {
// Split the full input name as such: deviceName.inputName
auto names = inputName.split('.');
UserInputMapper::DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) { if (names.size() >= 2) {
auto device = _registeredDevices.find(input.getDevice()); // Get the device name:
if (device != _registeredDevices.end()) { auto deviceName = names[0];
return (device->second); auto inputName = names[1];
int deviceID = findDevice(deviceName);
if (deviceID != Input::INVALID_DEVICE) {
const auto& deviceProxy = _registeredDevices.at(deviceID);
auto deviceInputs = deviceProxy->getAvailabeInputs();
for (auto input : deviceInputs) {
if (input.second == inputName) {
return input.first;
}
}
qCDebug(controllers) << "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(controllers) << "Couldn\'t find ActionChannel named <" << inputName << "> among actions";
} else {
qCDebug(controllers) << "Couldn\'t find InputDevice named <" << deviceName << ">";
}
} else { } else {
return DeviceProxy::Pointer(); qCDebug(controllers) << "Couldn\'t understand <" << inputName << "> as a valid inputDevice.inputName";
}
}
QString UserInputMapper::getDeviceName(uint16 deviceID) {
if (_registeredDevices.find(deviceID) != _registeredDevices.end()) {
return _registeredDevices[deviceID]->_name;
}
return QString("unknown");
}
void UserInputMapper::resetAllDeviceBindings() {
for (auto device : _registeredDevices) {
device.second->resetDeviceBindings();
}
}
void UserInputMapper::resetDevice(uint16 deviceID) {
auto device = _registeredDevices.find(deviceID);
if (device != _registeredDevices.end()) {
device->second->resetDeviceBindings();
}
}
int UserInputMapper::findDevice(QString name) {
for (auto device : _registeredDevices) {
if (device.second->_name.split(" (")[0] == name) {
return device.first;
}
}
return 0;
}
QVector<QString> UserInputMapper::getDeviceNames() {
QVector<QString> result;
for (auto device : _registeredDevices) {
QString deviceName = device.second->_name.split(" (")[0];
result << deviceName;
}
return result;
}
bool UserInputMapper::addInputChannel(Action action, const Input& input, float scale) {
return addInputChannel(action, input, Input(), scale);
}
bool UserInputMapper::addInputChannel(Action action, const Input& input, const Input& modifier, float scale) {
// Check that the device is registered
if (!getDeviceProxy(input)) {
qDebug() << "UserInputMapper::addInputChannel: The input comes from a device #" << input.getDevice() << "is unknown. no inputChannel mapped.";
return false;
}
auto inputChannel = InputChannel(input, modifier, action, scale);
// Insert or replace the input to modifiers
if (inputChannel.hasModifier()) {
auto& modifiers = _inputToModifiersMap[input.getID()];
modifiers.push_back(inputChannel._modifier);
std::sort(modifiers.begin(), modifiers.end());
} }
// Now update the action To Inputs side of things return Input();
_actionToInputsMap.insert(ActionToInputsMap::value_type(action, inputChannel));
return true;
} }
int UserInputMapper::addInputChannels(const InputChannels& channels) {
int nbAdded = 0;
for (auto& channel : channels) { bool UserInputMapper::addInputChannel(Action action, const Input& input, float scale) {
nbAdded += addInputChannel(channel._action, channel._input, channel._modifier, channel._scale); return addInputChannel(action, input, Input(), scale);
} }
return nbAdded;
} bool UserInputMapper::addInputChannel(Action action, const Input& input, const Input& modifier, float scale) {
// Check that the device is registered
bool UserInputMapper::removeInputChannel(InputChannel inputChannel) { if (!getDeviceProxy(input)) {
// Remove from Input to Modifiers map qDebug() << "UserInputMapper::addInputChannel: The input comes from a device #" << input.getDevice() << "is unknown. no inputChannel mapped.";
if (inputChannel.hasModifier()) { return false;
_inputToModifiersMap.erase(inputChannel._input.getID()); }
}
auto inputChannel = InputChannel(input, modifier, action, scale);
// Remove from Action to Inputs map
std::pair<ActionToInputsMap::iterator, ActionToInputsMap::iterator> ret; // Insert or replace the input to modifiers
ret = _actionToInputsMap.equal_range(inputChannel._action); if (inputChannel.hasModifier()) {
for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) { auto& modifiers = _inputToModifiersMap[input.getID()];
if (it->second == inputChannel) { modifiers.push_back(inputChannel._modifier);
_actionToInputsMap.erase(it); std::sort(modifiers.begin(), modifiers.end());
return true; }
}
} // Now update the action To Inputs side of things
_actionToInputsMap.insert(ActionToInputsMap::value_type(action, inputChannel));
return false;
} return true;
}
void UserInputMapper::removeAllInputChannels() {
_inputToModifiersMap.clear(); int UserInputMapper::addInputChannels(const InputChannels& channels) {
_actionToInputsMap.clear(); int nbAdded = 0;
} for (auto& channel : channels) {
nbAdded += addInputChannel(channel._action, channel._input, channel._modifier, channel._scale);
void UserInputMapper::removeAllInputChannelsForDevice(uint16 device) { }
QVector<InputChannel> channels = getAllInputsForDevice(device); return nbAdded;
for (auto& channel : channels) { }
removeInputChannel(channel);
} bool UserInputMapper::removeInputChannel(InputChannel inputChannel) {
} // Remove from Input to Modifiers map
if (inputChannel.hasModifier()) {
void UserInputMapper::removeDevice(int device) { _inputToModifiersMap.erase(inputChannel._input.getID());
removeAllInputChannelsForDevice((uint16) device); }
_registeredDevices.erase(device);
} // Remove from Action to Inputs map
std::pair<ActionToInputsMap::iterator, ActionToInputsMap::iterator> ret;
int UserInputMapper::getInputChannels(InputChannels& channels) const { ret = _actionToInputsMap.equal_range(inputChannel._action);
for (auto& channel : _actionToInputsMap) { for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) {
channels.push_back(channel.second); if (it->second == inputChannel) {
} _actionToInputsMap.erase(it);
return true;
return _actionToInputsMap.size(); }
} }
QVector<UserInputMapper::InputChannel> UserInputMapper::getAllInputsForDevice(uint16 device) { return false;
InputChannels allChannels; }
getInputChannels(allChannels);
void UserInputMapper::removeAllInputChannels() {
QVector<InputChannel> channels; _inputToModifiersMap.clear();
for (InputChannel inputChannel : allChannels) { _actionToInputsMap.clear();
if (inputChannel._input._device == device) { }
channels.push_back(inputChannel);
} void UserInputMapper::removeAllInputChannelsForDevice(uint16 device) {
} QVector<InputChannel> channels = getAllInputsForDevice(device);
for (auto& channel : channels) {
return channels; removeInputChannel(channel);
} }
}
void UserInputMapper::update(float deltaTime) {
void UserInputMapper::removeDevice(int device) {
// Reset the axis state for next loop removeAllInputChannelsForDevice((uint16) device);
for (auto& channel : _actionStates) { _registeredDevices.erase(device);
channel = 0.0f; }
}
int UserInputMapper::getInputChannels(InputChannels& channels) const {
for (auto& channel : _poseStates) { for (auto& channel : _actionToInputsMap) {
channel = PoseValue(); channels.push_back(channel.second);
} }
int currentTimestamp = 0; return _actionToInputsMap.size();
}
for (auto& channelInput : _actionToInputsMap) {
auto& inputMapping = channelInput.second; QVector<UserInputMapper::InputChannel> UserInputMapper::getAllInputsForDevice(uint16 device) {
auto& inputID = inputMapping._input; InputChannels allChannels;
bool enabled = true; getInputChannels(allChannels);
// Check if this input channel has modifiers and collect the possibilities QVector<InputChannel> channels;
auto modifiersIt = _inputToModifiersMap.find(inputID.getID()); for (InputChannel inputChannel : allChannels) {
if (modifiersIt != _inputToModifiersMap.end()) { if (inputChannel._input._device == device) {
Modifiers validModifiers; channels.push_back(inputChannel);
bool isActiveModifier = false; }
for (auto& modifier : modifiersIt->second) { }
auto deviceProxy = getDeviceProxy(modifier);
if (deviceProxy->getButton(modifier, currentTimestamp)) { return channels;
validModifiers.push_back(modifier); }
isActiveModifier |= (modifier.getID() == inputMapping._modifier.getID());
} void UserInputMapper::update(float deltaTime) {
}
enabled = (validModifiers.empty() && !inputMapping.hasModifier()) || isActiveModifier; // Reset the axis state for next loop
} for (auto& channel : _actionStates) {
channel = 0.0f;
// if enabled: default input or all modifiers on }
if (enabled) {
auto deviceProxy = getDeviceProxy(inputID); for (auto& channel : _poseStates) {
switch (inputMapping._input.getType()) { channel = PoseValue();
case ChannelType::BUTTON: { }
_actionStates[channelInput.first] += inputMapping._scale * float(deviceProxy->getButton(inputID, currentTimestamp));// * deltaTime; // weight the impulse by the deltaTime
break; int currentTimestamp = 0;
}
case ChannelType::AXIS: { for (auto& channelInput : _actionToInputsMap) {
_actionStates[channelInput.first] += inputMapping._scale * deviceProxy->getAxis(inputID, currentTimestamp); auto& inputMapping = channelInput.second;
break; auto& inputID = inputMapping._input;
} bool enabled = true;
case ChannelType::POSE: {
if (!_poseStates[channelInput.first].isValid()) { // Check if this input channel has modifiers and collect the possibilities
_poseStates[channelInput.first] = deviceProxy->getPose(inputID, currentTimestamp); auto modifiersIt = _inputToModifiersMap.find(inputID.getID());
} if (modifiersIt != _inputToModifiersMap.end()) {
break; Modifiers validModifiers;
} bool isActiveModifier = false;
default: { for (auto& modifier : modifiersIt->second) {
break; //silence please auto deviceProxy = getDeviceProxy(modifier);
} if (deviceProxy->getButton(modifier, currentTimestamp)) {
} validModifiers.push_back(modifier);
} else{ isActiveModifier |= (modifier.getID() == inputMapping._modifier.getID());
// Channel input not enabled }
enabled = false; }
} enabled = (validModifiers.empty() && !inputMapping.hasModifier()) || isActiveModifier;
} }
// Scale all the channel step with the scale // if enabled: default input or all modifiers on
static const float EPSILON = 0.01f; if (enabled) {
for (auto i = 0; i < NUM_ACTIONS; i++) { auto deviceProxy = getDeviceProxy(inputID);
_actionStates[i] *= _actionScales[i]; switch (inputMapping._input.getType()) {
// Emit only on change, and emit when moving back to 0 case ChannelType::BUTTON: {
if (fabsf(_actionStates[i] - _lastActionStates[i]) > EPSILON) { _actionStates[channelInput.first] += inputMapping._scale * float(deviceProxy->getButton(inputID, currentTimestamp));// * deltaTime; // weight the impulse by the deltaTime
_lastActionStates[i] = _actionStates[i]; break;
emit actionEvent(i, _actionStates[i]); }
} case ChannelType::AXIS: {
// TODO: emit signal for pose changes _actionStates[channelInput.first] += inputMapping._scale * deviceProxy->getAxis(inputID, currentTimestamp);
} break;
} }
case ChannelType::POSE: {
QVector<UserInputMapper::Action> UserInputMapper::getAllActions() const { if (!_poseStates[channelInput.first].isValid()) {
QVector<Action> actions; _poseStates[channelInput.first] = deviceProxy->getPose(inputID, currentTimestamp);
for (auto i = 0; i < NUM_ACTIONS; i++) { }
actions.append(Action(i)); break;
} }
return actions; default: {
} break; //silence please
}
QVector<UserInputMapper::InputChannel> UserInputMapper::getInputChannelsForAction(UserInputMapper::Action action) { }
QVector<InputChannel> inputChannels; } else{
std::pair <ActionToInputsMap::iterator, ActionToInputsMap::iterator> ret; // Channel input not enabled
ret = _actionToInputsMap.equal_range(action); enabled = false;
for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) { }
inputChannels.append(it->second); }
}
return inputChannels; // Scale all the channel step with the scale
} static const float EPSILON = 0.01f;
for (auto i = 0; i < NUM_ACTIONS; i++) {
int UserInputMapper::findAction(const QString& actionName) const { _actionStates[i] *= _actionScales[i];
auto actions = getAllActions(); // Emit only on change, and emit when moving back to 0
for (auto action : actions) { if (fabsf(_actionStates[i] - _lastActionStates[i]) > EPSILON) {
if (getActionName(action) == actionName) { _lastActionStates[i] = _actionStates[i];
return action; emit actionEvent(i, _actionStates[i]);
} }
} // TODO: emit signal for pose changes
// If the action isn't found, return -1 }
return -1; }
}
QVector<UserInputMapper::Action> UserInputMapper::getAllActions() const {
QVector<QString> UserInputMapper::getActionNames() const { QVector<Action> actions;
QVector<QString> result; for (auto i = 0; i < NUM_ACTIONS; i++) {
for (auto i = 0; i < NUM_ACTIONS; i++) { actions.append(Action(i));
result << _actionNames[i]; }
} return actions;
return result; }
}
QVector<UserInputMapper::InputChannel> UserInputMapper::getInputChannelsForAction(UserInputMapper::Action action) {
void UserInputMapper::assignDefaulActionScales() { QVector<InputChannel> inputChannels;
_actionScales[LONGITUDINAL_BACKWARD] = 1.0f; // 1m per unit std::pair <ActionToInputsMap::iterator, ActionToInputsMap::iterator> ret;
_actionScales[LONGITUDINAL_FORWARD] = 1.0f; // 1m per unit ret = _actionToInputsMap.equal_range(action);
_actionScales[LATERAL_LEFT] = 1.0f; // 1m per unit for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) {
_actionScales[LATERAL_RIGHT] = 1.0f; // 1m per unit inputChannels.append(it->second);
_actionScales[VERTICAL_DOWN] = 1.0f; // 1m per unit }
_actionScales[VERTICAL_UP] = 1.0f; // 1m per unit return inputChannels;
_actionScales[YAW_LEFT] = 1.0f; // 1 degree per unit }
_actionScales[YAW_RIGHT] = 1.0f; // 1 degree per unit
_actionScales[PITCH_DOWN] = 1.0f; // 1 degree per unit int UserInputMapper::findAction(const QString& actionName) const {
_actionScales[PITCH_UP] = 1.0f; // 1 degree per unit auto actions = getAllActions();
_actionScales[BOOM_IN] = 0.5f; // .5m per unit for (auto action : actions) {
_actionScales[BOOM_OUT] = 0.5f; // .5m per unit if (getActionName(action) == actionName) {
_actionScales[LEFT_HAND] = 1.0f; // default return action;
_actionScales[RIGHT_HAND] = 1.0f; // default }
_actionScales[LEFT_HAND_CLICK] = 1.0f; // on }
_actionScales[RIGHT_HAND_CLICK] = 1.0f; // on // If the action isn't found, return -1
_actionStates[SHIFT] = 1.0f; // on return -1;
_actionStates[ACTION1] = 1.0f; // default }
_actionStates[ACTION2] = 1.0f; // default
_actionStates[TRANSLATE_X] = 1.0f; // default QVector<QString> UserInputMapper::getActionNames() const {
_actionStates[TRANSLATE_Y] = 1.0f; // default QVector<QString> result;
_actionStates[TRANSLATE_Z] = 1.0f; // default for (auto i = 0; i < NUM_ACTIONS; i++) {
_actionStates[ROLL] = 1.0f; // default result << _actionNames[i];
_actionStates[PITCH] = 1.0f; // default }
_actionStates[YAW] = 1.0f; // default return result;
} }
// This is only necessary as long as the actions are hardcoded void UserInputMapper::assignDefaulActionScales() {
// Eventually you can just add the string when you add the action _actionScales[LONGITUDINAL_BACKWARD] = 1.0f; // 1m per unit
void UserInputMapper::createActionNames() { _actionScales[LONGITUDINAL_FORWARD] = 1.0f; // 1m per unit
_actionNames[LONGITUDINAL_BACKWARD] = "LONGITUDINAL_BACKWARD"; _actionScales[LATERAL_LEFT] = 1.0f; // 1m per unit
_actionNames[LONGITUDINAL_FORWARD] = "LONGITUDINAL_FORWARD"; _actionScales[LATERAL_RIGHT] = 1.0f; // 1m per unit
_actionNames[LATERAL_LEFT] = "LATERAL_LEFT"; _actionScales[VERTICAL_DOWN] = 1.0f; // 1m per unit
_actionNames[LATERAL_RIGHT] = "LATERAL_RIGHT"; _actionScales[VERTICAL_UP] = 1.0f; // 1m per unit
_actionNames[VERTICAL_DOWN] = "VERTICAL_DOWN"; _actionScales[YAW_LEFT] = 1.0f; // 1 degree per unit
_actionNames[VERTICAL_UP] = "VERTICAL_UP"; _actionScales[YAW_RIGHT] = 1.0f; // 1 degree per unit
_actionNames[YAW_LEFT] = "YAW_LEFT"; _actionScales[PITCH_DOWN] = 1.0f; // 1 degree per unit
_actionNames[YAW_RIGHT] = "YAW_RIGHT"; _actionScales[PITCH_UP] = 1.0f; // 1 degree per unit
_actionNames[PITCH_DOWN] = "PITCH_DOWN"; _actionScales[BOOM_IN] = 0.5f; // .5m per unit
_actionNames[PITCH_UP] = "PITCH_UP"; _actionScales[BOOM_OUT] = 0.5f; // .5m per unit
_actionNames[BOOM_IN] = "BOOM_IN"; _actionScales[LEFT_HAND] = 1.0f; // default
_actionNames[BOOM_OUT] = "BOOM_OUT"; _actionScales[RIGHT_HAND] = 1.0f; // default
_actionNames[LEFT_HAND] = "LEFT_HAND"; _actionScales[LEFT_HAND_CLICK] = 1.0f; // on
_actionNames[RIGHT_HAND] = "RIGHT_HAND"; _actionScales[RIGHT_HAND_CLICK] = 1.0f; // on
_actionNames[LEFT_HAND_CLICK] = "LEFT_HAND_CLICK"; _actionStates[SHIFT] = 1.0f; // on
_actionNames[RIGHT_HAND_CLICK] = "RIGHT_HAND_CLICK"; _actionStates[ACTION1] = 1.0f; // default
_actionNames[SHIFT] = "SHIFT"; _actionStates[ACTION2] = 1.0f; // default
_actionNames[ACTION1] = "ACTION1"; _actionStates[TRANSLATE_X] = 1.0f; // default
_actionNames[ACTION2] = "ACTION2"; _actionStates[TRANSLATE_Y] = 1.0f; // default
_actionNames[CONTEXT_MENU] = "CONTEXT_MENU"; _actionStates[TRANSLATE_Z] = 1.0f; // default
_actionNames[TOGGLE_MUTE] = "TOGGLE_MUTE"; _actionStates[ROLL] = 1.0f; // default
_actionNames[TRANSLATE_X] = "TranslateX"; _actionStates[PITCH] = 1.0f; // default
_actionNames[TRANSLATE_Y] = "TranslateY"; _actionStates[YAW] = 1.0f; // default
_actionNames[TRANSLATE_Z] = "TranslateZ"; }
_actionNames[ROLL] = "Roll";
_actionNames[PITCH] = "Pitch"; // This is only necessary as long as the actions are hardcoded
_actionNames[YAW] = "Yaw"; // Eventually you can just add the string when you add the action
} void UserInputMapper::createActionNames() {
_actionNames[LONGITUDINAL_BACKWARD] = "LONGITUDINAL_BACKWARD";
void UserInputMapper::registerStandardDevice() { _actionNames[LONGITUDINAL_FORWARD] = "LONGITUDINAL_FORWARD";
_standardController = std::make_shared<StandardController>(); _actionNames[LATERAL_LEFT] = "LATERAL_LEFT";
_standardController->registerToUserInputMapper(*this); _actionNames[LATERAL_RIGHT] = "LATERAL_RIGHT";
} _actionNames[VERTICAL_DOWN] = "VERTICAL_DOWN";
_actionNames[VERTICAL_UP] = "VERTICAL_UP";
float UserInputMapper::DeviceProxy::getValue(const Input& input, int timestamp) const { _actionNames[YAW_LEFT] = "YAW_LEFT";
switch (input.getType()) { _actionNames[YAW_RIGHT] = "YAW_RIGHT";
case UserInputMapper::ChannelType::BUTTON: _actionNames[PITCH_DOWN] = "PITCH_DOWN";
return getButton(input, timestamp) ? 1.0f : 0.0f; _actionNames[PITCH_UP] = "PITCH_UP";
_actionNames[BOOM_IN] = "BOOM_IN";
case UserInputMapper::ChannelType::AXIS: _actionNames[BOOM_OUT] = "BOOM_OUT";
return getAxis(input, timestamp); _actionNames[LEFT_HAND] = "LEFT_HAND";
_actionNames[RIGHT_HAND] = "RIGHT_HAND";
case UserInputMapper::ChannelType::POSE: _actionNames[LEFT_HAND_CLICK] = "LEFT_HAND_CLICK";
return getPose(input, timestamp)._valid ? 1.0f : 0.0f; _actionNames[RIGHT_HAND_CLICK] = "RIGHT_HAND_CLICK";
_actionNames[SHIFT] = "SHIFT";
default: _actionNames[ACTION1] = "ACTION1";
return 0.0f; _actionNames[ACTION2] = "ACTION2";
} _actionNames[CONTEXT_MENU] = "CONTEXT_MENU";
} _actionNames[TOGGLE_MUTE] = "TOGGLE_MUTE";
_actionNames[TRANSLATE_X] = "TranslateX";
_actionNames[TRANSLATE_Y] = "TranslateY";
_actionNames[TRANSLATE_Z] = "TranslateZ";
_actionNames[ROLL] = "Roll";
_actionNames[PITCH] = "Pitch";
_actionNames[YAW] = "Yaw";
}
void UserInputMapper::registerStandardDevice() {
_standardController = std::make_shared<StandardController>();
_standardController->registerToUserInputMapper(*this);
}
float UserInputMapper::DeviceProxy::getValue(const Input& input, int timestamp) const {
switch (input.getType()) {
case UserInputMapper::ChannelType::BUTTON:
return getButton(input, timestamp) ? 1.0f : 0.0f;
case UserInputMapper::ChannelType::AXIS:
return getAxis(input, timestamp);
case UserInputMapper::ChannelType::POSE:
return getPose(input, timestamp)._valid ? 1.0f : 0.0f;
default:
return 0.0f;
}
}

View file

@ -85,6 +85,7 @@ public:
static const uint16 INVALID_DEVICE; static const uint16 INVALID_DEVICE;
static const uint16 INVALID_CHANNEL; static const uint16 INVALID_CHANNEL;
static const uint16 INVALID_TYPE; static const uint16 INVALID_TYPE;
static const uint16 ACTIONS_DEVICE;
}; };
@ -119,9 +120,11 @@ public:
class DeviceProxy { class DeviceProxy {
public: 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; } const QString& getName() const { return _name; }
QString _baseName;
QString _name; QString _name;
ButtonGetter getButton = [] (const Input& input, int timestamp) -> bool { return false; }; ButtonGetter getButton = [] (const Input& input, int timestamp) -> bool { return false; };
AxisGetter getAxis = [] (const Input& input, int timestamp) -> float { return 0.0f; }; AxisGetter getAxis = [] (const Input& input, int timestamp) -> float { return 0.0f; };
@ -141,9 +144,12 @@ public:
QVector<InputPair> getAvailableInputs(uint16 deviceID) { return _registeredDevices[deviceID]->getAvailabeInputs(); } QVector<InputPair> getAvailableInputs(uint16 deviceID) { return _registeredDevices[deviceID]->getAvailabeInputs(); }
void resetAllDeviceBindings(); void resetAllDeviceBindings();
void resetDevice(uint16 deviceID); void resetDevice(uint16 deviceID);
int findDevice(QString name); int findDevice(QString name) const;
QVector<QString> getDeviceNames(); QVector<QString> getDeviceNames();
Input findDeviceInput(const QString& inputName) const;
// Actions are the output channels of the Mapper, that's what the InputChannel map to // Actions are the output channels of the Mapper, that's what the InputChannel map to
// For now the Actions are hardcoded, this is bad, but we will fix that in the near future // For now the Actions are hardcoded, this is bad, but we will fix that in the near future
enum Action { enum Action {

View file

@ -11,11 +11,15 @@
#include <QtCore/QHash> #include <QtCore/QHash>
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include "RouteBuilderProxy.h" #include "RouteBuilderProxy.h"
#include "../ScriptingInterface.h" #include "../ScriptingInterface.h"
#include "../Logging.h" #include "../Logging.h"
namespace controller { using namespace controller;
QObject* MappingBuilderProxy::from(const QJSValue& source) { QObject* MappingBuilderProxy::from(const QJSValue& source) {
qCDebug(controllers) << "Creating new Route builder proxy from " << source.toString(); qCDebug(controllers) << "Creating new Route builder proxy from " << source.toString();
@ -41,9 +45,48 @@ QObject* MappingBuilderProxy::join(const QJSValue& source1, const QJSValue& sour
return from(_parent.compositeEndpointFor(source1Endpoint, source2Endpoint)); return from(_parent.compositeEndpointFor(source1Endpoint, source2Endpoint));
} }
const QString JSON_NAME = QStringLiteral("name");
const QString JSON_CHANNELS = QStringLiteral("channels");
const QString JSON_CHANNEL_FROM = QStringLiteral("from");
const QString JSON_CHANNEL_TO = QStringLiteral("to");
const QString JSON_CHANNEL_FILTERS = QStringLiteral("filters");
void MappingBuilderProxy::parse(const QJsonObject& json) {
_mapping->_name = json[JSON_NAME].toString();
_mapping->_channelMappings.clear();
const auto& jsonChannels = json[JSON_CHANNELS].toArray();
for (const auto& channelIt : jsonChannels) {
parseRoute(channelIt);
}
}
void MappingBuilderProxy::parseRoute(const QJsonValue& json) {
if (json.isObject()) {
const auto& jsonChannel = json.toObject();
auto newRoute = from(jsonChannel[JSON_CHANNEL_FROM]);
if (newRoute) {
auto route = dynamic_cast<RouteBuilderProxy*>(newRoute);
route->filters(jsonChannel[JSON_CHANNEL_FILTERS]);
route->to(jsonChannel[JSON_CHANNEL_TO]);
}
}
}
QObject* MappingBuilderProxy::from(const QJsonValue& json) {
if (json.isString()) {
return from(_parent.endpointFor(_parent.inputFor(json.toString())));
} else if (json.isObject()) {
// Endpoint is defined as an object, we expect a js function then
return nullptr;
}
}
QObject* MappingBuilderProxy::enable(bool enable) { QObject* MappingBuilderProxy::enable(bool enable) {
_parent.enableMapping(_mapping->_name, enable); _parent.enableMapping(_mapping->_name, enable);
return this; return this;
} }
}

View file

@ -17,6 +17,7 @@
class QJSValue; class QJSValue;
class QScriptValue; class QScriptValue;
class QJsonValue;
namespace controller { namespace controller {
@ -33,15 +34,28 @@ public:
Q_INVOKABLE QObject* from(const QJSValue& source); Q_INVOKABLE QObject* from(const QJSValue& source);
Q_INVOKABLE QObject* from(const QScriptValue& source); Q_INVOKABLE QObject* from(const QScriptValue& source);
Q_INVOKABLE QObject* join(const QJSValue& source1, const QJSValue& source2); Q_INVOKABLE QObject* join(const QJSValue& source1, const QJSValue& source2);
Q_INVOKABLE QObject* enable(bool enable = true); Q_INVOKABLE QObject* enable(bool enable = true);
Q_INVOKABLE QObject* disable() { return enable(false); } Q_INVOKABLE QObject* disable() { return enable(false); }
// JSON route creation point
Q_INVOKABLE QObject* from(const QJsonValue& json);
void parse(const QJsonObject& json);
// void serialize(QJsonObject& json);
protected: protected:
QObject* from(const Endpoint::Pointer& source); QObject* from(const Endpoint::Pointer& source);
friend class RouteBuilderProxy; friend class RouteBuilderProxy;
ScriptingInterface& _parent; ScriptingInterface& _parent;
Mapping::Pointer _mapping; Mapping::Pointer _mapping;
void parseRoute(const QJsonValue& json);
}; };
} }

View file

@ -9,6 +9,9 @@
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include "MappingBuilderProxy.h" #include "MappingBuilderProxy.h"
@ -54,84 +57,41 @@ QObject* RouteBuilderProxy::filter(const QScriptValue& expression) {
QObject* RouteBuilderProxy::clamp(float min, float max) { QObject* RouteBuilderProxy::clamp(float min, float max) {
addFilter([=](float value) { addFilter(Filter::Pointer(new ClampFilter(min, max)));
return glm::clamp(value, min, max);
});
return this; return this;
} }
QObject* RouteBuilderProxy::scale(float multiplier) { QObject* RouteBuilderProxy::scale(float multiplier) {
addFilter([=](float value) { addFilter(Filter::Pointer(new ScaleFilter(multiplier)));
return value * multiplier;
});
return this; return this;
} }
QObject* RouteBuilderProxy::invert() { QObject* RouteBuilderProxy::invert() {
return scale(-1.0f); addFilter(Filter::Pointer(new InvertFilter()));
return this;
} }
QObject* RouteBuilderProxy::deadZone(float min) { QObject* RouteBuilderProxy::deadZone(float min) {
assert(min < 1.0f); addFilter(Filter::Pointer(new DeadZoneFilter(min)));
float scale = 1.0f / (1.0f - min);
addFilter([=](float value) {
if (abs(value) < min) {
return 0.0f;
}
return (value - min) * scale;
});
return this; return this;
} }
QObject* RouteBuilderProxy::constrainToInteger() { QObject* RouteBuilderProxy::constrainToInteger() {
addFilter([=](float value) { addFilter(Filter::Pointer(new ConstrainToIntegerFilter()));
return glm::sign(value);
});
return this; return this;
} }
QObject* RouteBuilderProxy::constrainToPositiveInteger() { QObject* RouteBuilderProxy::constrainToPositiveInteger() {
addFilter([=](float value) { addFilter(Filter::Pointer(new ConstrainToPositiveIntegerFilter()));
return (value <= 0.0f) ? 0.0f : 1.0f;
});
return this; return this;
} }
class PulseFilter : public Filter {
public:
PulseFilter(float interval) : _interval(interval) {}
virtual float apply(float value) const override {
float result = 0.0;
if (0.0 != value) {
float now = secTimestampNow();
float delta = now - _lastEmitTime;
if (delta >= _interval) {
_lastEmitTime = now;
result = value;
}
}
return result;
}
private:
mutable float _lastEmitTime{ -std::numeric_limits<float>::max() };
const float _interval;
};
QObject* RouteBuilderProxy::pulse(float interval) { QObject* RouteBuilderProxy::pulse(float interval) {
Filter::Pointer filter = std::make_shared<PulseFilter>(interval); addFilter(Filter::Pointer(new PulseFilter(interval)));
addFilter(filter);
return this; return this;
} }
void RouteBuilderProxy::addFilter(Filter::Lambda lambda) { void RouteBuilderProxy::addFilter(Filter::Lambda lambda) {
Filter::Pointer filterPointer = std::make_shared < LambdaFilter > (lambda); Filter::Pointer filterPointer = std::make_shared < LambdaFilter > (lambda);
addFilter(filterPointer); addFilter(filterPointer);
@ -141,4 +101,34 @@ void RouteBuilderProxy::addFilter(Filter::Pointer filter) {
_route->_filters.push_back(filter); _route->_filters.push_back(filter);
} }
QObject* RouteBuilderProxy::filters(const QJsonValue& json) {
// We expect an array of objects to define the filters
if (json.isArray()) {
const auto& jsonFilters = json.toArray();
for (auto jsonFilter : jsonFilters) {
if (jsonFilter.isObject()) {
// The filter is an object, now let s check for type and potential arguments
Filter::Pointer filter = Filter::parse(jsonFilter.toObject());
if (filter) {
addFilter(filter);
}
}
}
}
return this;
}
void RouteBuilderProxy::to(const QJsonValue& json) {
if (json.isString()) {
return to(_parent.endpointFor(_parent.inputFor(json.toString())));
} else if (json.isObject()) {
// Endpoint is defined as an object, we expect a js function then
//return to((Endpoint*) nullptr);
}
}
} }

View file

@ -16,6 +16,7 @@
class QJSValue; class QJSValue;
class QScriptValue; class QScriptValue;
class QJsonValue;
namespace controller { namespace controller {
@ -42,6 +43,10 @@ class RouteBuilderProxy : public QObject {
Q_INVOKABLE QObject* constrainToInteger(); Q_INVOKABLE QObject* constrainToInteger();
Q_INVOKABLE QObject* constrainToPositiveInteger(); Q_INVOKABLE QObject* constrainToPositiveInteger();
// JSON route creation point
Q_INVOKABLE QObject* filters(const QJsonValue& json);
Q_INVOKABLE void to(const QJsonValue& json);
private: private:
void to(const Endpoint::Pointer& destination); void to(const Endpoint::Pointer& destination);
void addFilter(Filter::Lambda lambda); void addFilter(Filter::Lambda lambda);

View file

@ -91,7 +91,10 @@ int main(int argc, char** argv) {
for (auto path : qApp->libraryPaths()) { for (auto path : qApp->libraryPaths()) {
qDebug() << path; qDebug() << path;
} }
for (auto path : qApp->libraryPaths()) {
qDebug() << path;
}
QTimer timer; QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [] { QObject::connect(&timer, &QTimer::timeout, [] {
@ -108,7 +111,7 @@ int main(int argc, char** argv) {
userInputMapper->update(delta); userInputMapper->update(delta);
}); });
timer.start(50); timer.start(50);
{ {
DependencyManager::set<UserInputMapper>(); DependencyManager::set<UserInputMapper>();
foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) {