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 <QtScript/QScriptValue>
namespace controller {
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
Filter::Pointer Filter::parse(const QJsonObject& json) {
// FIXME parse the json object and determine the instance type to create
return Filter::Pointer();
#include "SharedUtil.h"
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 <numeric>
#include <functional>
#include <map>
#include <GLMHelpers.h>
#include <QtCore/QEasingCurve>
class QJsonObject;
class QJsonArray;
namespace controller {
@ -30,18 +35,84 @@ namespace controller {
using List = std::list<Pointer>;
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 {
public:
// LambdaFilter() {}
LambdaFilter(Lambda f) : _function(f) {};
virtual float apply(float value) const {
return _function(value);
}
virtual bool parseParameters(const QJsonArray& parameters) { return true; }
// REGISTER_FILTER_CLASS(LambdaFilter);
private:
Lambda _function;
};
@ -50,45 +121,94 @@ namespace controller {
public:
};
class ScaleFilter : public Filter {
public:
REGISTER_FILTER_CLASS(ScaleFilter, "scale");
ScaleFilter() {}
ScaleFilter(float scale): _scale(scale) {}
//class ScaleFilter : public Filter {
//public:
// ScaleFilter(float scale);
// virtual float apply(float scale) const override {
// return value * _scale;
// }
virtual float apply(float value) const override {
return value * _scale;
}
virtual bool parseParameters(const QJsonArray& parameters);
//private:
// const float _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<float>::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 AbstractRangeFilter {
////public:
//// DeadzoneFilter(float min, float max = 1.0f);
//// virtual float apply(float newValue, float oldValue) override;
////};
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, "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 {
//public:
@ -117,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;

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"
namespace controller {

View file

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

View file

@ -12,6 +12,9 @@
#include <QtCore/QRegularExpression>
#include <QJsonDocument>
#include <QJsonObject>
#include <GLMHelpers.h>
#include <DependencyManager.h>
@ -19,8 +22,6 @@
#include "Logging.h"
#include "InputDevice.h"
static const uint16_t ACTIONS_DEVICE = UserInputMapper::Input::INVALID_DEVICE - (uint16_t)1;
namespace controller {
class VirtualEndpoint : public Endpoint {
@ -154,7 +155,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
QString cleanActionName = QString(actionName).remove(ScriptingInterface::SANITIZE_NAME_EXPRESSION);
@ -176,6 +177,34 @@ namespace controller {
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) {
auto iterator = _mappingsByName.find(mappingName);
if (_mappingsByName.end() == iterator) {
@ -318,6 +347,10 @@ namespace controller {
return Endpoint::Pointer();
}
UserInputMapper::Input ScriptingInterface::inputFor(const QString& inputName) {
return DependencyManager::get<UserInputMapper>()->findDeviceInput(inputName);
}
Endpoint::Pointer ScriptingInterface::endpointFor(const UserInputMapper::Input& inputId) {
auto iterator = _endpoints.find(inputId);
if (_endpoints.end() == iterator) {

View file

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

View file

@ -1,382 +1,424 @@
//
// UserInputMapper.cpp
// input-plugins/src/input-plugins
//
// Created by Sam Gateau on 4/27/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
//
#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;
//
// UserInputMapper.cpp
// input-plugins/src/input-plugins
//
// Created by Sam Gateau on 4/27/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
//
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) {
int numberOfType = recordDeviceOfType(proxy->_name);
UserInputMapper::~UserInputMapper() {
}
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;
qCDebug(controllers) << "Registered input device <" << proxy->_name << "> deviceID = " << deviceID;
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) {
auto device = _registeredDevices.find(input.getDevice());
if (device != _registeredDevices.end()) {
return (device->second);
if (names.size() >= 2) {
// Get the device name:
auto deviceName = names[0];
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 {
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) {
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());
qCDebug(controllers) << "Couldn\'t understand <" << inputName << "> as a valid inputDevice.inputName";
}
// Now update the action To Inputs side of things
_actionToInputsMap.insert(ActionToInputsMap::value_type(action, inputChannel));
return true;
return Input();
}
int UserInputMapper::addInputChannels(const InputChannels& channels) {
int nbAdded = 0;
for (auto& channel : channels) {
nbAdded += addInputChannel(channel._action, channel._input, channel._modifier, channel._scale);
}
return nbAdded;
}
bool UserInputMapper::removeInputChannel(InputChannel inputChannel) {
// Remove from Input to Modifiers map
if (inputChannel.hasModifier()) {
_inputToModifiersMap.erase(inputChannel._input.getID());
}
// Remove from Action to Inputs map
std::pair<ActionToInputsMap::iterator, ActionToInputsMap::iterator> ret;
ret = _actionToInputsMap.equal_range(inputChannel._action);
for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) {
if (it->second == inputChannel) {
_actionToInputsMap.erase(it);
return true;
}
}
return false;
}
void UserInputMapper::removeAllInputChannels() {
_inputToModifiersMap.clear();
_actionToInputsMap.clear();
}
void UserInputMapper::removeAllInputChannelsForDevice(uint16 device) {
QVector<InputChannel> channels = getAllInputsForDevice(device);
for (auto& channel : channels) {
removeInputChannel(channel);
}
}
void UserInputMapper::removeDevice(int device) {
removeAllInputChannelsForDevice((uint16) device);
_registeredDevices.erase(device);
}
int UserInputMapper::getInputChannels(InputChannels& channels) const {
for (auto& channel : _actionToInputsMap) {
channels.push_back(channel.second);
}
return _actionToInputsMap.size();
}
QVector<UserInputMapper::InputChannel> UserInputMapper::getAllInputsForDevice(uint16 device) {
InputChannels allChannels;
getInputChannels(allChannels);
QVector<InputChannel> channels;
for (InputChannel inputChannel : allChannels) {
if (inputChannel._input._device == device) {
channels.push_back(inputChannel);
}
}
return channels;
}
void UserInputMapper::update(float deltaTime) {
// Reset the axis state for next loop
for (auto& channel : _actionStates) {
channel = 0.0f;
}
for (auto& channel : _poseStates) {
channel = PoseValue();
}
int currentTimestamp = 0;
for (auto& channelInput : _actionToInputsMap) {
auto& inputMapping = channelInput.second;
auto& inputID = inputMapping._input;
bool enabled = true;
// Check if this input channel has modifiers and collect the possibilities
auto modifiersIt = _inputToModifiersMap.find(inputID.getID());
if (modifiersIt != _inputToModifiersMap.end()) {
Modifiers validModifiers;
bool isActiveModifier = false;
for (auto& modifier : modifiersIt->second) {
auto deviceProxy = getDeviceProxy(modifier);
if (deviceProxy->getButton(modifier, currentTimestamp)) {
validModifiers.push_back(modifier);
isActiveModifier |= (modifier.getID() == inputMapping._modifier.getID());
}
}
enabled = (validModifiers.empty() && !inputMapping.hasModifier()) || isActiveModifier;
}
// if enabled: default input or all modifiers on
if (enabled) {
auto deviceProxy = getDeviceProxy(inputID);
switch (inputMapping._input.getType()) {
case ChannelType::BUTTON: {
_actionStates[channelInput.first] += inputMapping._scale * float(deviceProxy->getButton(inputID, currentTimestamp));// * deltaTime; // weight the impulse by the deltaTime
break;
}
case ChannelType::AXIS: {
_actionStates[channelInput.first] += inputMapping._scale * deviceProxy->getAxis(inputID, currentTimestamp);
break;
}
case ChannelType::POSE: {
if (!_poseStates[channelInput.first].isValid()) {
_poseStates[channelInput.first] = deviceProxy->getPose(inputID, currentTimestamp);
}
break;
}
default: {
break; //silence please
}
}
} else{
// Channel input not enabled
enabled = false;
}
}
// Scale all the channel step with the scale
static const float EPSILON = 0.01f;
for (auto i = 0; i < NUM_ACTIONS; i++) {
_actionStates[i] *= _actionScales[i];
// Emit only on change, and emit when moving back to 0
if (fabsf(_actionStates[i] - _lastActionStates[i]) > EPSILON) {
_lastActionStates[i] = _actionStates[i];
emit actionEvent(i, _actionStates[i]);
}
// TODO: emit signal for pose changes
}
}
QVector<UserInputMapper::Action> UserInputMapper::getAllActions() const {
QVector<Action> actions;
for (auto i = 0; i < NUM_ACTIONS; i++) {
actions.append(Action(i));
}
return actions;
}
QVector<UserInputMapper::InputChannel> UserInputMapper::getInputChannelsForAction(UserInputMapper::Action action) {
QVector<InputChannel> inputChannels;
std::pair <ActionToInputsMap::iterator, ActionToInputsMap::iterator> ret;
ret = _actionToInputsMap.equal_range(action);
for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) {
inputChannels.append(it->second);
}
return inputChannels;
}
int UserInputMapper::findAction(const QString& actionName) const {
auto actions = getAllActions();
for (auto action : actions) {
if (getActionName(action) == actionName) {
return action;
}
}
// If the action isn't found, return -1
return -1;
}
QVector<QString> UserInputMapper::getActionNames() const {
QVector<QString> result;
for (auto i = 0; i < NUM_ACTIONS; i++) {
result << _actionNames[i];
}
return result;
}
void UserInputMapper::assignDefaulActionScales() {
_actionScales[LONGITUDINAL_BACKWARD] = 1.0f; // 1m per unit
_actionScales[LONGITUDINAL_FORWARD] = 1.0f; // 1m per unit
_actionScales[LATERAL_LEFT] = 1.0f; // 1m per unit
_actionScales[LATERAL_RIGHT] = 1.0f; // 1m per unit
_actionScales[VERTICAL_DOWN] = 1.0f; // 1m per unit
_actionScales[VERTICAL_UP] = 1.0f; // 1m per unit
_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
_actionScales[PITCH_UP] = 1.0f; // 1 degree per unit
_actionScales[BOOM_IN] = 0.5f; // .5m per unit
_actionScales[BOOM_OUT] = 0.5f; // .5m per unit
_actionScales[LEFT_HAND] = 1.0f; // default
_actionScales[RIGHT_HAND] = 1.0f; // default
_actionScales[LEFT_HAND_CLICK] = 1.0f; // on
_actionScales[RIGHT_HAND_CLICK] = 1.0f; // on
_actionStates[SHIFT] = 1.0f; // on
_actionStates[ACTION1] = 1.0f; // default
_actionStates[ACTION2] = 1.0f; // default
_actionStates[TRANSLATE_X] = 1.0f; // default
_actionStates[TRANSLATE_Y] = 1.0f; // default
_actionStates[TRANSLATE_Z] = 1.0f; // default
_actionStates[ROLL] = 1.0f; // default
_actionStates[PITCH] = 1.0f; // default
_actionStates[YAW] = 1.0f; // default
}
// This is only necessary as long as the actions are hardcoded
// Eventually you can just add the string when you add the action
void UserInputMapper::createActionNames() {
_actionNames[LONGITUDINAL_BACKWARD] = "LONGITUDINAL_BACKWARD";
_actionNames[LONGITUDINAL_FORWARD] = "LONGITUDINAL_FORWARD";
_actionNames[LATERAL_LEFT] = "LATERAL_LEFT";
_actionNames[LATERAL_RIGHT] = "LATERAL_RIGHT";
_actionNames[VERTICAL_DOWN] = "VERTICAL_DOWN";
_actionNames[VERTICAL_UP] = "VERTICAL_UP";
_actionNames[YAW_LEFT] = "YAW_LEFT";
_actionNames[YAW_RIGHT] = "YAW_RIGHT";
_actionNames[PITCH_DOWN] = "PITCH_DOWN";
_actionNames[PITCH_UP] = "PITCH_UP";
_actionNames[BOOM_IN] = "BOOM_IN";
_actionNames[BOOM_OUT] = "BOOM_OUT";
_actionNames[LEFT_HAND] = "LEFT_HAND";
_actionNames[RIGHT_HAND] = "RIGHT_HAND";
_actionNames[LEFT_HAND_CLICK] = "LEFT_HAND_CLICK";
_actionNames[RIGHT_HAND_CLICK] = "RIGHT_HAND_CLICK";
_actionNames[SHIFT] = "SHIFT";
_actionNames[ACTION1] = "ACTION1";
_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;
}
}
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
_actionToInputsMap.insert(ActionToInputsMap::value_type(action, inputChannel));
return true;
}
int UserInputMapper::addInputChannels(const InputChannels& channels) {
int nbAdded = 0;
for (auto& channel : channels) {
nbAdded += addInputChannel(channel._action, channel._input, channel._modifier, channel._scale);
}
return nbAdded;
}
bool UserInputMapper::removeInputChannel(InputChannel inputChannel) {
// Remove from Input to Modifiers map
if (inputChannel.hasModifier()) {
_inputToModifiersMap.erase(inputChannel._input.getID());
}
// Remove from Action to Inputs map
std::pair<ActionToInputsMap::iterator, ActionToInputsMap::iterator> ret;
ret = _actionToInputsMap.equal_range(inputChannel._action);
for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) {
if (it->second == inputChannel) {
_actionToInputsMap.erase(it);
return true;
}
}
return false;
}
void UserInputMapper::removeAllInputChannels() {
_inputToModifiersMap.clear();
_actionToInputsMap.clear();
}
void UserInputMapper::removeAllInputChannelsForDevice(uint16 device) {
QVector<InputChannel> channels = getAllInputsForDevice(device);
for (auto& channel : channels) {
removeInputChannel(channel);
}
}
void UserInputMapper::removeDevice(int device) {
removeAllInputChannelsForDevice((uint16) device);
_registeredDevices.erase(device);
}
int UserInputMapper::getInputChannels(InputChannels& channels) const {
for (auto& channel : _actionToInputsMap) {
channels.push_back(channel.second);
}
return _actionToInputsMap.size();
}
QVector<UserInputMapper::InputChannel> UserInputMapper::getAllInputsForDevice(uint16 device) {
InputChannels allChannels;
getInputChannels(allChannels);
QVector<InputChannel> channels;
for (InputChannel inputChannel : allChannels) {
if (inputChannel._input._device == device) {
channels.push_back(inputChannel);
}
}
return channels;
}
void UserInputMapper::update(float deltaTime) {
// Reset the axis state for next loop
for (auto& channel : _actionStates) {
channel = 0.0f;
}
for (auto& channel : _poseStates) {
channel = PoseValue();
}
int currentTimestamp = 0;
for (auto& channelInput : _actionToInputsMap) {
auto& inputMapping = channelInput.second;
auto& inputID = inputMapping._input;
bool enabled = true;
// Check if this input channel has modifiers and collect the possibilities
auto modifiersIt = _inputToModifiersMap.find(inputID.getID());
if (modifiersIt != _inputToModifiersMap.end()) {
Modifiers validModifiers;
bool isActiveModifier = false;
for (auto& modifier : modifiersIt->second) {
auto deviceProxy = getDeviceProxy(modifier);
if (deviceProxy->getButton(modifier, currentTimestamp)) {
validModifiers.push_back(modifier);
isActiveModifier |= (modifier.getID() == inputMapping._modifier.getID());
}
}
enabled = (validModifiers.empty() && !inputMapping.hasModifier()) || isActiveModifier;
}
// if enabled: default input or all modifiers on
if (enabled) {
auto deviceProxy = getDeviceProxy(inputID);
switch (inputMapping._input.getType()) {
case ChannelType::BUTTON: {
_actionStates[channelInput.first] += inputMapping._scale * float(deviceProxy->getButton(inputID, currentTimestamp));// * deltaTime; // weight the impulse by the deltaTime
break;
}
case ChannelType::AXIS: {
_actionStates[channelInput.first] += inputMapping._scale * deviceProxy->getAxis(inputID, currentTimestamp);
break;
}
case ChannelType::POSE: {
if (!_poseStates[channelInput.first].isValid()) {
_poseStates[channelInput.first] = deviceProxy->getPose(inputID, currentTimestamp);
}
break;
}
default: {
break; //silence please
}
}
} else{
// Channel input not enabled
enabled = false;
}
}
// Scale all the channel step with the scale
static const float EPSILON = 0.01f;
for (auto i = 0; i < NUM_ACTIONS; i++) {
_actionStates[i] *= _actionScales[i];
// Emit only on change, and emit when moving back to 0
if (fabsf(_actionStates[i] - _lastActionStates[i]) > EPSILON) {
_lastActionStates[i] = _actionStates[i];
emit actionEvent(i, _actionStates[i]);
}
// TODO: emit signal for pose changes
}
}
QVector<UserInputMapper::Action> UserInputMapper::getAllActions() const {
QVector<Action> actions;
for (auto i = 0; i < NUM_ACTIONS; i++) {
actions.append(Action(i));
}
return actions;
}
QVector<UserInputMapper::InputChannel> UserInputMapper::getInputChannelsForAction(UserInputMapper::Action action) {
QVector<InputChannel> inputChannels;
std::pair <ActionToInputsMap::iterator, ActionToInputsMap::iterator> ret;
ret = _actionToInputsMap.equal_range(action);
for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) {
inputChannels.append(it->second);
}
return inputChannels;
}
int UserInputMapper::findAction(const QString& actionName) const {
auto actions = getAllActions();
for (auto action : actions) {
if (getActionName(action) == actionName) {
return action;
}
}
// If the action isn't found, return -1
return -1;
}
QVector<QString> UserInputMapper::getActionNames() const {
QVector<QString> result;
for (auto i = 0; i < NUM_ACTIONS; i++) {
result << _actionNames[i];
}
return result;
}
void UserInputMapper::assignDefaulActionScales() {
_actionScales[LONGITUDINAL_BACKWARD] = 1.0f; // 1m per unit
_actionScales[LONGITUDINAL_FORWARD] = 1.0f; // 1m per unit
_actionScales[LATERAL_LEFT] = 1.0f; // 1m per unit
_actionScales[LATERAL_RIGHT] = 1.0f; // 1m per unit
_actionScales[VERTICAL_DOWN] = 1.0f; // 1m per unit
_actionScales[VERTICAL_UP] = 1.0f; // 1m per unit
_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
_actionScales[PITCH_UP] = 1.0f; // 1 degree per unit
_actionScales[BOOM_IN] = 0.5f; // .5m per unit
_actionScales[BOOM_OUT] = 0.5f; // .5m per unit
_actionScales[LEFT_HAND] = 1.0f; // default
_actionScales[RIGHT_HAND] = 1.0f; // default
_actionScales[LEFT_HAND_CLICK] = 1.0f; // on
_actionScales[RIGHT_HAND_CLICK] = 1.0f; // on
_actionStates[SHIFT] = 1.0f; // on
_actionStates[ACTION1] = 1.0f; // default
_actionStates[ACTION2] = 1.0f; // default
_actionStates[TRANSLATE_X] = 1.0f; // default
_actionStates[TRANSLATE_Y] = 1.0f; // default
_actionStates[TRANSLATE_Z] = 1.0f; // default
_actionStates[ROLL] = 1.0f; // default
_actionStates[PITCH] = 1.0f; // default
_actionStates[YAW] = 1.0f; // default
}
// This is only necessary as long as the actions are hardcoded
// Eventually you can just add the string when you add the action
void UserInputMapper::createActionNames() {
_actionNames[LONGITUDINAL_BACKWARD] = "LONGITUDINAL_BACKWARD";
_actionNames[LONGITUDINAL_FORWARD] = "LONGITUDINAL_FORWARD";
_actionNames[LATERAL_LEFT] = "LATERAL_LEFT";
_actionNames[LATERAL_RIGHT] = "LATERAL_RIGHT";
_actionNames[VERTICAL_DOWN] = "VERTICAL_DOWN";
_actionNames[VERTICAL_UP] = "VERTICAL_UP";
_actionNames[YAW_LEFT] = "YAW_LEFT";
_actionNames[YAW_RIGHT] = "YAW_RIGHT";
_actionNames[PITCH_DOWN] = "PITCH_DOWN";
_actionNames[PITCH_UP] = "PITCH_UP";
_actionNames[BOOM_IN] = "BOOM_IN";
_actionNames[BOOM_OUT] = "BOOM_OUT";
_actionNames[LEFT_HAND] = "LEFT_HAND";
_actionNames[RIGHT_HAND] = "RIGHT_HAND";
_actionNames[LEFT_HAND_CLICK] = "LEFT_HAND_CLICK";
_actionNames[RIGHT_HAND_CLICK] = "RIGHT_HAND_CLICK";
_actionNames[SHIFT] = "SHIFT";
_actionNames[ACTION1] = "ACTION1";
_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_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; };
@ -141,9 +144,12 @@ public:
QVector<InputPair> getAvailableInputs(uint16 deviceID) { return _registeredDevices[deviceID]->getAvailabeInputs(); }
void resetAllDeviceBindings();
void resetDevice(uint16 deviceID);
int findDevice(QString name);
int findDevice(QString name) const;
QVector<QString> getDeviceNames();
Input findDeviceInput(const QString& inputName) const;
// 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
enum Action {

View file

@ -11,11 +11,15 @@
#include <QtCore/QHash>
#include <QtCore/QDebug>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include "RouteBuilderProxy.h"
#include "../ScriptingInterface.h"
#include "../Logging.h"
namespace controller {
using namespace controller;
QObject* MappingBuilderProxy::from(const QJSValue& source) {
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));
}
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) {
_parent.enableMapping(_mapping->_name, enable);
return this;
}
}

View file

@ -17,6 +17,7 @@
class QJSValue;
class QScriptValue;
class QJsonValue;
namespace controller {
@ -33,15 +34,28 @@ public:
Q_INVOKABLE QObject* from(const QJSValue& source);
Q_INVOKABLE QObject* from(const QScriptValue& source);
Q_INVOKABLE QObject* join(const QJSValue& source1, const QJSValue& source2);
Q_INVOKABLE QObject* enable(bool enable = true);
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:
QObject* from(const Endpoint::Pointer& source);
friend class RouteBuilderProxy;
ScriptingInterface& _parent;
Mapping::Pointer _mapping;
void parseRoute(const QJsonValue& json);
};
}

View file

@ -9,6 +9,9 @@
#include <QtCore/QDebug>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <GLMHelpers.h>
#include "MappingBuilderProxy.h"
@ -54,84 +57,41 @@ 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;
}
QObject* RouteBuilderProxy::scale(float multiplier) {
addFilter([=](float value) {
return value * multiplier;
});
addFilter(Filter::Pointer(new ScaleFilter(multiplier)));
return this;
}
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;
}
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) {
Filter::Pointer filter = std::make_shared<PulseFilter>(interval);
addFilter(filter);
addFilter(Filter::Pointer(new PulseFilter(interval)));
return this;
}
void RouteBuilderProxy::addFilter(Filter::Lambda lambda) {
Filter::Pointer filterPointer = std::make_shared < LambdaFilter > (lambda);
addFilter(filterPointer);
@ -141,4 +101,34 @@ void RouteBuilderProxy::addFilter(Filter::Pointer 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 QScriptValue;
class QJsonValue;
namespace controller {
@ -42,6 +43,10 @@ class RouteBuilderProxy : public QObject {
Q_INVOKABLE QObject* constrainToInteger();
Q_INVOKABLE QObject* constrainToPositiveInteger();
// JSON route creation point
Q_INVOKABLE QObject* filters(const QJsonValue& json);
Q_INVOKABLE void to(const QJsonValue& json);
private:
void to(const Endpoint::Pointer& destination);
void addFilter(Filter::Lambda lambda);

View file

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