From 184e9a2209df5d4b6f7981d9bc76c41ca3b4ac88 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 9 Oct 2015 16:20:55 -0700 Subject: [PATCH] Prototyping controllers --- interface/CMakeLists.txt | 3 +- .../src/controllers/ControllerMapping.h | 149 ++++++++++++++++++ .../controllers/src/controllers/Endpoint.cpp | 58 +++++++ .../controllers/src/controllers/Endpoint.h | 34 ++++ 4 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 libraries/controllers/src/controllers/ControllerMapping.h create mode 100644 libraries/controllers/src/controllers/Endpoint.cpp create mode 100644 libraries/controllers/src/controllers/Endpoint.h diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 63d1445496..2ce7f9a76d 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -118,7 +118,8 @@ target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES}) link_hifi_libraries(shared octree environment gpu procedural model render fbx networking model-networking entities avatars audio audio-client animation script-engine physics render-utils entities-renderer ui auto-updater - plugins display-plugins input-plugins) + plugins display-plugins input-plugins + controllers) add_dependency_external_projects(sdl2) diff --git a/libraries/controllers/src/controllers/ControllerMapping.h b/libraries/controllers/src/controllers/ControllerMapping.h new file mode 100644 index 0000000000..ade9984309 --- /dev/null +++ b/libraries/controllers/src/controllers/ControllerMapping.h @@ -0,0 +1,149 @@ +#include +#include + +#include +#include + +extern float currentTime(); + +namespace Controllers { + + + /* + * A function which provides input + */ + class FunctionEndpoint : public Endpoint { + public: + + virtual float value() override { + float now = currentTime(); + float delta = now - _lastCalled; + float result = _inputFunction.call(_object, QScriptValue(delta)).toNumber(); + _lastCalled = now; + return result; + } + + virtual void apply(float newValue, float oldValue, const Endpoint& source) override { + if (newValue != oldValue) { + //_outputFunction.call(newValue, oldValue, source); + } + } + + float _lastValue{ NAN }; + float _lastCalled{ 0 }; + QScriptValue _outputFunction; + QScriptValue _inputFunction; + QScriptValue _object; + }; + + + // Encapsulates part of a filter chain + class Filter { + public: + virtual float apply(float newValue, float oldValue) = 0; + }; + + class ScaleFilter : public Filter { + public: + virtual float apply(float newValue, float oldValue) { + return newValue * _scale; + } + + float _scale{ 1.0 }; + }; + + class PulseFilter : public Filter { + public: + virtual float apply(float newValue, float oldValue) { + // ??? + } + + float _lastEmitValue{ 0 }; + float _lastEmitTime{ 0 }; + float _interval{ -1.0f }; + }; + + using FilterList = std::list; + + /* + * encapsulates a source, destination and filters to apply + */ + class Route { + public: + Endpoint* _source; + Endpoint* _destination; + FilterList _filters; + }; + + using ValueMap = std::map; + + class Mapping { + public: + // List of routes + using List = std::list; + // Map of source channels to route lists + using Map = std::map; + + Map _channelMappings; + ValueMap _lastValues; + }; + + class MappingsStack { + std::list _stack; + ValueMap _lastValues; + + void update() { + EndpointList hardwareInputs = getHardwareEndpoints(); + ValueMap currentValues; + + for (auto input : hardwareInputs) { + currentValues[input] = input->value(); + } + + // Now process the current values for each level of the stack + for (auto& mapping : _stack) { + update(mapping, currentValues); + } + + _lastValues = currentValues; + } + + void update(Mapping& mapping, ValueMap& values) { + ValueMap updates; + EndpointList consumedEndpoints; + for (const auto& entry : values) { + Endpoint* endpoint = entry.first; + if (!mapping._channelMappings.count(endpoint)) { + continue; + } + + const Mapping::List& routes = mapping._channelMappings[endpoint]; + consumedEndpoints.push_back(endpoint); + for (const auto& route : routes) { + float lastValue = 0; + if (mapping._lastValues.count(endpoint)) { + lastValue = mapping._lastValues[endpoint]; + } + float value = entry.second; + for (const auto& filter : route._filters) { + value = filter->apply(value, lastValue); + } + updates[route._destination] = value; + } + } + + // Update the last seen values + mapping._lastValues = values; + + // Remove all the consumed inputs + for (auto endpoint : consumedEndpoints) { + values.erase(endpoint); + } + + // Add all the updates (may restore some of the consumed data if a passthrough was created (i.e. source == dest) + for (const auto& entry : updates) { + values[entry.first] = entry.second; + } + } + }; +} diff --git a/libraries/controllers/src/controllers/Endpoint.cpp b/libraries/controllers/src/controllers/Endpoint.cpp new file mode 100644 index 0000000000..5f9e0b0efa --- /dev/null +++ b/libraries/controllers/src/controllers/Endpoint.cpp @@ -0,0 +1,58 @@ +// +// 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 "Endpoint.h" + +#include +#include + +namespace Controllers { + + // FIXME how do we handle dynamic changes in connected hardware? + const Endpoint::List& Endpoint::getHardwareEndpoints() { + static Endpoint::List ACTIVE_HARDWARE; + static std::once_flag once; + std::call_once(once, [&] { + auto userInputMapper = DependencyManager::get(); + // TODO populate ACTIVE_HARDWARE with all the connected devices + // For each connected device + // for each input channel + // build a HardwareEndpoint instance around the input channel and add it to the list + }); + } + + // Ex: xbox.RY, xbox.A .... + class HardwareEndpoint : public Endpoint { + public: + virtual float value() override { + // ... + } + + virtual void apply(float newValue, float oldValue, const Endpoint& source) override { + // Default does nothing, but in theory this could be something like vibration + // mapping.from(xbox.X).to(xbox.Vibrate) + } + }; + + // Ex: Standard.RY, Action.Yaw + class VirtualEndpoint : public Endpoint { + public: + virtual void apply(float newValue) { + if (newValue != _lastValue) { + _lastValue = newValue; + } + } + + virtual float value() { + return _lastValue; + } + + float _lastValue; + }; + +} diff --git a/libraries/controllers/src/controllers/Endpoint.h b/libraries/controllers/src/controllers/Endpoint.h new file mode 100644 index 0000000000..7361c15706 --- /dev/null +++ b/libraries/controllers/src/controllers/Endpoint.h @@ -0,0 +1,34 @@ +// +// 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 +// + +#pragma once +#ifndef hifi_Controllers_Endpoint_h +#define hifi_Controllers_Endpoint_h + +#include +#include + +namespace Controllers { + /* + * Encapsulates a particular input / output, + * i.e. Hydra.Button0, Standard.X, Action.Yaw + */ + class Endpoint { + public: + virtual float value() = 0; + virtual void apply(float newValue, float oldValue, const Endpoint& source) = 0; + + using Pointer = std::shared_ptr; + using List = std::list; + + static const List& getHardwareEndpoints(); + }; + +} + +#endif