mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-07 15:30:42 +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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
Loading…
Reference in a new issue