mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-10 01:06:25 +02:00
Merge pull request #6081 from samcake/controllers
Controllers mapping created from JSON, first draft
This commit is contained in:
commit
1f6a2f3c21
14 changed files with 901 additions and 475 deletions
71
examples/controllers/controllerMappings.js
Normal file
71
examples/controllers/controllerMappings.js
Normal 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]);
|
||||
});
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
@ -51,44 +122,93 @@ namespace controller {
|
|||
|
||||
};
|
||||
|
||||
//class ScaleFilter : public Filter {
|
||||
//public:
|
||||
// ScaleFilter(float scale);
|
||||
// virtual float apply(float scale) const override {
|
||||
// return value * _scale;
|
||||
// }
|
||||
class ScaleFilter : public Filter {
|
||||
public:
|
||||
REGISTER_FILTER_CLASS(ScaleFilter, "scale");
|
||||
ScaleFilter() {}
|
||||
ScaleFilter(float scale): _scale(scale) {}
|
||||
|
||||
//private:
|
||||
// const float _scale;
|
||||
//};
|
||||
virtual float apply(float value) const override {
|
||||
return value * _scale;
|
||||
}
|
||||
virtual bool parseParameters(const QJsonArray& parameters);
|
||||
|
||||
//class AbstractRangeFilter : public Filter {
|
||||
//public:
|
||||
// RangeFilter(float min, float max) : _min(min), _max(max) {}
|
||||
private:
|
||||
float _scale = 1.0f;
|
||||
};
|
||||
|
||||
//protected:
|
||||
// const float _min;
|
||||
// const float _max;
|
||||
//};
|
||||
class InvertFilter : public ScaleFilter {
|
||||
public:
|
||||
REGISTER_FILTER_CLASS(InvertFilter, "invert");
|
||||
InvertFilter() : ScaleFilter(-1.0f) {}
|
||||
|
||||
///*
|
||||
//* 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;
|
||||
virtual bool parseParameters(const QJsonArray& parameters) { return true; }
|
||||
|
||||
//private:
|
||||
// float _lastEmitTime{ -std::numeric_limits<float>::max() };
|
||||
// const float _interval;
|
||||
//};
|
||||
private:
|
||||
};
|
||||
|
||||
////class DeadzoneFilter : public AbstractRangeFilter {
|
||||
////public:
|
||||
//// DeadzoneFilter(float min, float max = 1.0f);
|
||||
//// virtual float apply(float newValue, float oldValue) override;
|
||||
////};
|
||||
class ClampFilter : public Filter {
|
||||
public:
|
||||
REGISTER_FILTER_CLASS(ClampFilter, "clamp");
|
||||
ClampFilter(float min = 0.0, float max = 1.0) : _min(min), _max(max) {};
|
||||
|
||||
virtual float apply(float value) const override {
|
||||
return glm::clamp(value, _min, _max);
|
||||
}
|
||||
virtual bool parseParameters(const QJsonArray& parameters) override;
|
||||
protected:
|
||||
float _min = 0.0f;
|
||||
float _max = 1.0f;
|
||||
};
|
||||
|
||||
class DeadZoneFilter : public Filter {
|
||||
public:
|
||||
REGISTER_FILTER_CLASS(DeadZoneFilter, "deadZone");
|
||||
DeadZoneFilter(float min = 0.0) : _min(min) {};
|
||||
|
||||
virtual float apply(float value) const override;
|
||||
virtual bool parseParameters(const QJsonArray& parameters) override;
|
||||
protected:
|
||||
float _min = 0.0f;
|
||||
};
|
||||
|
||||
class PulseFilter : public Filter {
|
||||
public:
|
||||
REGISTER_FILTER_CLASS(PulseFilter, "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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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:
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
@ -121,6 +122,8 @@ namespace controller {
|
|||
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;
|
||||
QVariantMap _standard;
|
||||
|
|
|
@ -12,10 +12,13 @@
|
|||
#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() {
|
||||
|
@ -28,23 +31,10 @@ UserInputMapper::~UserInputMapper() {
|
|||
}
|
||||
|
||||
|
||||
int UserInputMapper::recordDeviceOfType(const QString& deviceName) {
|
||||
if (!_deviceCounts.contains(deviceName)) {
|
||||
_deviceCounts[deviceName] = 0;
|
||||
}
|
||||
_deviceCounts[deviceName] += 1;
|
||||
|
||||
return _deviceCounts[deviceName];
|
||||
}
|
||||
|
||||
bool UserInputMapper::registerDevice(uint16 deviceID, const DeviceProxy::Pointer& proxy) {
|
||||
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;
|
||||
qCDebug(controllers) << "Registered input device <" << proxy->_name << "> deviceID = " << deviceID;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -78,13 +68,19 @@ void UserInputMapper::resetDevice(uint16 deviceID) {
|
|||
}
|
||||
}
|
||||
|
||||
int UserInputMapper::findDevice(QString name) {
|
||||
int UserInputMapper::findDevice(QString name) const {
|
||||
if (_standardDevice && (_standardDevice->getName() == name)) {
|
||||
return getStandardDeviceID();
|
||||
}
|
||||
|
||||
for (auto device : _registeredDevices) {
|
||||
if (device.second->_name.split(" (")[0] == name) {
|
||||
return device.first;
|
||||
} else if (device.second->_baseName == name) {
|
||||
return device.first;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return Input::INVALID_DEVICE;
|
||||
}
|
||||
|
||||
QVector<QString> UserInputMapper::getDeviceNames() {
|
||||
|
@ -96,6 +92,52 @@ QVector<QString> UserInputMapper::getDeviceNames() {
|
|||
return result;
|
||||
}
|
||||
|
||||
UserInputMapper::Input UserInputMapper::findDeviceInput(const QString& inputName) const {
|
||||
|
||||
// Split the full input name as such: deviceName.inputName
|
||||
auto names = inputName.split('.');
|
||||
|
||||
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 {
|
||||
qCDebug(controllers) << "Couldn\'t understand <" << inputName << "> as a valid inputDevice.inputName";
|
||||
}
|
||||
|
||||
return Input();
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool UserInputMapper::addInputChannel(Action action, const Input& input, float scale) {
|
||||
return addInputChannel(action, input, Input(), scale);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -92,6 +92,9 @@ int main(int argc, char** argv) {
|
|||
qDebug() << path;
|
||||
}
|
||||
|
||||
for (auto path : qApp->libraryPaths()) {
|
||||
qDebug() << path;
|
||||
}
|
||||
|
||||
QTimer timer;
|
||||
QObject::connect(&timer, &QTimer::timeout, [] {
|
||||
|
|
Loading…
Reference in a new issue