Merge branch 'controllers' of https://github.com/highfidelity/hifi into controllers

This commit is contained in:
samcake 2015-10-23 18:18:27 -07:00
commit eae316100a
44 changed files with 880 additions and 373 deletions

View file

@ -148,6 +148,7 @@ function MyController(hand, triggerAction) {
this.state = STATE_OFF;
this.pointer = null; // entity-id of line object
this.triggerValue = 0; // rolling average of trigger value
this.rawTriggerValue = 0;
var _this = this;
@ -244,12 +245,17 @@ function MyController(hand, triggerAction) {
this.pointer = null;
};
this.updateSmoothedTrigger = function() {
var triggerValue = Controller.getValue(this.triggerAction);
this.eitherTrigger = function (value) {
_this.rawTriggerValue = value;
};
this.updateSmoothedTrigger = function () {
var triggerValue = this.rawTriggerValue;
// smooth out trigger value
this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) +
(triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO));
}
};
this.triggerSmoothedSqueezed = function() {
return this.triggerValue > TRIGGER_ON_VALUE;
@ -259,8 +265,8 @@ function MyController(hand, triggerAction) {
return this.triggerValue < TRIGGER_OFF_VALUE;
};
this.triggerSqueezed = function() {
var triggerValue = Controller.getValue(this.triggerAction);
this.triggerSqueezed = function() {
var triggerValue = this.rawTriggerValue;
return triggerValue > TRIGGER_ON_VALUE;
};
@ -861,6 +867,14 @@ function MyController(hand, triggerAction) {
var rightController = new MyController(RIGHT_HAND, Controller.Standard.RT);
var leftController = new MyController(LEFT_HAND, Controller.Standard.LT);
var MAPPING_NAME = "com.highfidelity.handControllerGrab";
var mapping = Controller.newMapping(MAPPING_NAME);
mapping.from([Controller.Standard.RB, Controller.Standard.RT]).to(rightController.eitherTrigger);
mapping.from([Controller.Standard.LB, Controller.Standard.LT]).to(leftController.eitherTrigger);
Controller.enableMapping(MAPPING_NAME);
function update() {
rightController.update();
leftController.update();
@ -869,6 +883,7 @@ function update() {
function cleanup() {
rightController.cleanup();
leftController.cleanup();
Controller.disableMapping(MAPPING_NAME);
}
Script.scriptEnding.connect(cleanup);

View file

@ -20,10 +20,11 @@ var BALL_SIZE = 0.08;
var PADDLE_SIZE = 0.20;
var PADDLE_THICKNESS = 0.06;
var PADDLE_COLOR = { red: 184, green: 134, blue: 11 };
var BALL_COLOR = { red: 255, green: 0, blue: 0 };
var BALL_COLOR = { red: 0, green: 255, blue: 0 };
var LINE_COLOR = { red: 255, green: 255, blue: 0 };
var PADDLE_BOX_OFFSET = { x: 0.05, y: 0.0, z: 0.0 };
//probably we need to fix these initial values (offsets and orientation)
var HOLD_POSITION_LEFT_OFFSET = { x: -0.15, y: 0.05, z: -0.05 };
var HOLD_POSITION_RIGHT_OFFSET = { x: -0.15, y: 0.05, z: 0.05 };
var PADDLE_ORIENTATION = Quat.fromPitchYawRollDegrees(0,0,0);
@ -32,18 +33,7 @@ var SPRING_FORCE = 15.0;
var lastSoundTime = 0;
var gameOn = false;
var leftHanded = true;
var controllerID;
function setControllerID() {
if (leftHanded) {
controllerID = 1;
} else {
controllerID = 3;
}
}
setControllerID();
Menu.addMenu("PaddleBall");
Menu.addMenuItem({ menuName: "PaddleBall", menuItemName: "Left-Handed", isCheckable: true, isChecked: true });
@ -63,7 +53,7 @@ var ball, paddle, paddleModel, line;
function createEntities() {
ball = Entities.addEntity(
{ type: "Sphere",
position: Controller.getSpatialControlPosition(controllerID),
position: leftHanded ? MyAvatar.leftHandPose.translation : MyAvatar.rightHandPose.translation,
dimensions: { x: BALL_SIZE, y: BALL_SIZE, z: BALL_SIZE },
color: BALL_COLOR,
gravity: { x: 0, y: GRAVITY, z: 0 },
@ -73,28 +63,28 @@ function createEntities() {
paddle = Entities.addEntity(
{ type: "Box",
position: Controller.getSpatialControlPosition(controllerID),
position: leftHanded ? MyAvatar.leftHandPose.translation : MyAvatar.rightHandPose.translation,
dimensions: { x: PADDLE_SIZE, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 0.80 },
color: PADDLE_COLOR,
gravity: { x: 0, y: 0, z: 0 },
ignoreCollisions: false,
damping: 0.10,
visible: false,
rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)),
collisionsWillMove: false });
rotation : leftHanded ? MyAvatar.leftHandPose.rotation : MyAvatar.rightHandPose.rotation,
collisionsWillMove: false });
modelURL = "http://public.highfidelity.io/models/attachments/pong_paddle.fbx";
paddleModel = Entities.addEntity(
{ type: "Model",
position: Vec3.sum(Controller.getSpatialControlPosition(controllerID), PADDLE_BOX_OFFSET),
position: Vec3.sum( leftHanded ? MyAvatar.leftHandPose.translation : MyAvatar.rightHandPose.translation, PADDLE_BOX_OFFSET),
dimensions: { x: PADDLE_SIZE * 1.5, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 1.25 },
color: PADDLE_COLOR,
gravity: { x: 0, y: 0, z: 0 },
ignoreCollisions: true,
modelURL: modelURL,
damping: 0.10,
rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)),
collisionsWillMove: false });
rotation : leftHanded ? MyAvatar.leftHandPose.rotation : MyAvatar.rightHandPose.rotation,
collisionsWillMove: false });
line = Overlays.addOverlay("line3d", {
start: { x: 0, y: 0, z: 0 },
@ -118,7 +108,7 @@ function deleteEntities() {
}
function update(deltaTime) {
var palmPosition = Controller.getSpatialControlPosition(controllerID);
var palmPosition = leftHanded ? MyAvatar.leftHandPose.translation : MyAvatar.rightHandPose.translation;
var controllerActive = (Vec3.length(palmPosition) > 0);
if (!gameOn && controllerActive) {
@ -133,8 +123,8 @@ function update(deltaTime) {
}
var paddleOrientation = leftHanded ? PADDLE_ORIENTATION : Quat.multiply(PADDLE_ORIENTATION, Quat.fromPitchYawRollDegrees(0, 180, 0));
var paddleWorldOrientation = Quat.multiply(Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)), paddleOrientation);
var holdPosition = Vec3.sum(leftHanded ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(),
var paddleWorldOrientation = Quat.multiply(leftHanded ? MyAvatar.leftHandPose.rotation : MyAvatar.rightHandPose.rotation, paddleOrientation);
var holdPosition = Vec3.sum(leftHanded ? MyAvatar.leftHandPose.translation : MyAvatar.rightHandPose.translation,
Vec3.multiplyQbyV(paddleWorldOrientation, leftHanded ? HOLD_POSITION_LEFT_OFFSET : HOLD_POSITION_RIGHT_OFFSET ));
var props = Entities.getEntityProperties(ball);
@ -146,10 +136,10 @@ function update(deltaTime) {
Entities.editEntity(ball, { velocity: ballVelocity });
Overlays.editOverlay(line, { start: props.position, end: holdPosition });
Entities.editEntity(paddle, { position: holdPosition,
velocity: Controller.getSpatialControlVelocity(controllerID),
velocity: leftHanded ? MyAvatar.leftHandPose.velocity : MyAvatar.rightHandPose.velocity,
rotation: paddleWorldOrientation });
Entities.editEntity(paddleModel, { position: Vec3.sum(holdPosition, Vec3.multiplyQbyV(paddleWorldOrientation, PADDLE_BOX_OFFSET)),
velocity: Controller.getSpatialControlVelocity(controllerID),
velocity: leftHanded ? MyAvatar.leftHandPose.velocity : MyAvatar.rightHandPose.velocity,
rotation: paddleWorldOrientation });
}
@ -182,7 +172,6 @@ function menuItemEvent(menuItem) {
leftHanded = Menu.isOptionChecked("Left-Handed");
}
if ((leftHanded != oldHanded) && gameOn) {
setControllerID();
deleteEntities();
createEntities();
}

View file

@ -59,9 +59,7 @@ function controller(side) {
this.triggerHeld = false;
this.triggerThreshold = 0.9;
this.side = side;
this.palm = 2 * side;
this.tip = 2 * side + 1;
this.trigger = side;
this.trigger = side == LEFT ? Controller.Standard.LT : Controller.Standard.RT;
this.originalGravity = {
x: 0,
y: 0,
@ -150,8 +148,8 @@ function controller(side) {
this.updateControllerState = function() {
this.palmPosition = Controller.getSpatialControlPosition(this.palm);
this.tipPosition = Controller.getSpatialControlPosition(this.tip);
this.palmPosition = this.side == RIGHT ? MyAvatar.rightHandPose.translation : MyAvatar.leftHandPose.translation;
this.tipPosition = this.side == RIGHT ? MyAvatar.rightHandTipPose.translation : MyAvatar.leftHandTipPose.translation;
this.triggerValue = Controller.getTriggerValue(this.trigger);
}

View file

@ -71,10 +71,8 @@ function controller(side, cycleColorButton) {
this.triggerHeld = false;
this.triggerThreshold = 0.9;
this.side = side;
this.palm = 2 * side;
this.tip = 2 * side + 1;
this.trigger = side;
this.cycleColorButton = cycleColorButton;
this.trigger = side == LEFT ? Controller.Stantard.LT : Controller.Standard.RT;
this.cycleColorButton = side == LEFT ? Controller.Stantard.LeftPrimaryThumb : Controller.Standard.RightPrimaryThumb;
this.points = [];
this.normals = [];
@ -173,11 +171,10 @@ function controller(side, cycleColorButton) {
this.updateControllerState = function() {
this.cycleColorButtonPressed = Controller.isButtonPressed(this.cycleColorButton);
this.palmPosition = Controller.getSpatialControlPosition(this.palm);
this.tipPosition = Controller.getSpatialControlPosition(this.tip);
this.palmNormal = Controller.getSpatialControlNormal(this.palm);
this.triggerValue = Controller.getTriggerValue(this.trigger);
this.cycleColorButtonPressed = Controller.getValue(this.cycleColorButton);
this.palmPosition = this.side == RIGHT ? MyAvatar.rightHandPose.translation : MyAvatar.leftHandPose.translation;
this.tipPosition = this.side == RIGHT ? MyAvatar.rightHandTipPose.translation : MyAvatar.leftHandTipPose.translation;
this.triggerValue = Controller.getValue(this.trigger);
if (this.prevCycleColorButtonPressed === true && this.cycleColorButtonPressed === false) {
@ -215,8 +212,8 @@ function vectorIsZero(v) {
}
var rightController = new controller(RIGHT, RIGHT_BUTTON_4);
var leftController = new controller(LEFT, LEFT_BUTTON_4);
var rightController = new controller(RIGHT);
var leftController = new controller(LEFT);
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);

View file

@ -4,8 +4,10 @@ ControllerTest = function() {
var xbox = Controller.Hardware.GamePad;
this.mappingEnabled = false;
this.mapping = Controller.newMapping();
this.mapping.from(standard.LX).when([standard.LB, standard.RB]).to(actions.Yaw);
this.mapping.from(standard.RX).to(actions.StepYaw);
this.mapping.from(standard.RY).invert().to(actions.Pitch);
this.mapping.from(standard.RY).invert().to(actions.Pitch);
var testMakeAxis = false;

View file

@ -24,9 +24,22 @@
#include "Logging.h"
#include "Endpoint.h"
#include "Route.h"
#include "Mapping.h"
#include "impl/conditionals/AndConditional.h"
#include "impl/conditionals/EndpointConditional.h"
#include "impl/conditionals/ScriptConditional.h"
#include "impl/endpoints/ActionEndpoint.h"
#include "impl/endpoints/AnyEndpoint.h"
#include "impl/endpoints/ArrayEndpoint.h"
#include "impl/endpoints/CompositeEndpoint.h"
#include "impl/endpoints/InputEndpoint.h"
#include "impl/endpoints/JSEndpoint.h"
#include "impl/endpoints/ScriptEndpoint.h"
#include "impl/endpoints/StandardEndpoint.h"
#include "impl/Route.h"
#include "impl/Mapping.h"
namespace controller {
const uint16_t UserInputMapper::ACTIONS_DEVICE = Input::INVALID_DEVICE - 0xFF;
@ -42,300 +55,6 @@ controller::UserInputMapper::UserInputMapper() {
namespace controller {
class ScriptEndpoint : public Endpoint {
Q_OBJECT;
public:
ScriptEndpoint(const QScriptValue& callable)
: Endpoint(Input::INVALID_INPUT), _callable(callable) {
}
virtual float value();
virtual void apply(float newValue, float oldValue, const Pointer& source);
protected:
Q_INVOKABLE void updateValue();
Q_INVOKABLE virtual void internalApply(float newValue, float oldValue, int sourceID);
private:
QScriptValue _callable;
float _lastValue = 0.0f;
};
class StandardEndpoint : public VirtualEndpoint {
public:
StandardEndpoint(const Input& input) : VirtualEndpoint(input) {}
virtual bool writeable() const override { return !_written; }
virtual bool readable() const override { return !_read; }
virtual void reset() override {
apply(0.0f, 0.0f, Endpoint::Pointer());
apply(Pose(), Pose(), Endpoint::Pointer());
_written = _read = false;
}
virtual float value() override {
_read = true;
return VirtualEndpoint::value();
}
virtual void apply(float newValue, float oldValue, const Pointer& source) override {
// For standard endpoints, the first NON-ZERO write counts.
if (newValue != 0.0) {
_written = true;
}
VirtualEndpoint::apply(newValue, oldValue, source);
}
virtual Pose pose() override {
_read = true;
return VirtualEndpoint::pose();
}
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override {
if (newValue != Pose()) {
_written = true;
}
VirtualEndpoint::apply(newValue, oldValue, source);
}
private:
bool _written { false };
bool _read { false };
};
class JSEndpoint : public Endpoint {
public:
JSEndpoint(const QJSValue& callable)
: Endpoint(Input::INVALID_INPUT), _callable(callable) {
}
virtual float value() {
float result = (float)_callable.call().toNumber();;
return result;
}
virtual void apply(float newValue, float oldValue, const Pointer& source) {
_callable.call(QJSValueList({ QJSValue(newValue) }));
}
private:
QJSValue _callable;
};
float ScriptEndpoint::value() {
updateValue();
return _lastValue;
}
void ScriptEndpoint::updateValue() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "updateValue", Qt::QueuedConnection);
return;
}
_lastValue = (float)_callable.call().toNumber();
}
void ScriptEndpoint::apply(float newValue, float oldValue, const Pointer& source) {
internalApply(newValue, oldValue, source->getInput().getID());
}
void ScriptEndpoint::internalApply(float newValue, float oldValue, int sourceID) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "internalApply", Qt::QueuedConnection,
Q_ARG(float, newValue),
Q_ARG(float, oldValue),
Q_ARG(int, sourceID));
return;
}
_callable.call(QScriptValue(),
QScriptValueList({ QScriptValue(newValue), QScriptValue(oldValue), QScriptValue(sourceID) }));
}
static const Input INVALID_STANDARD_INPUT = Input(UserInputMapper::STANDARD_DEVICE, Input::INVALID_CHANNEL, ChannelType::INVALID);
class CompositeEndpoint : public Endpoint, Endpoint::Pair {
public:
CompositeEndpoint(Endpoint::Pointer first, Endpoint::Pointer second)
: Endpoint(Input::INVALID_INPUT), Pair(first, second) {
if (first->getInput().device == UserInputMapper::STANDARD_DEVICE &&
second->getInput().device == UserInputMapper::STANDARD_DEVICE) {
this->_input = INVALID_STANDARD_INPUT;
}
}
virtual float value() {
float result = first->value() * -1.0f + second->value();
return result;
}
virtual void apply(float newValue, float oldValue, const Pointer& source) {
// Composites are read only
}
};
class ArrayEndpoint : public Endpoint {
friend class UserInputMapper;
public:
using Pointer = std::shared_ptr<ArrayEndpoint>;
ArrayEndpoint() : Endpoint(Input::INVALID_INPUT) { }
virtual float value() override {
return 0.0;
}
virtual void apply(float newValue, float oldValue, const Endpoint::Pointer& source) override {
for (auto& child : _children) {
if (child->writeable()) {
child->apply(newValue, oldValue, source);
}
}
}
virtual bool readable() const override { return false; }
private:
Endpoint::List _children;
};
class AnyEndpoint : public Endpoint {
friend class UserInputMapper;
public:
using Pointer = std::shared_ptr<AnyEndpoint>;
AnyEndpoint(Endpoint::List children) : Endpoint(Input::INVALID_INPUT), _children(children) {
bool standard = true;
// Ensure if we're building a composite of standard devices the composite itself
// is treated as a standard device for rule processing order
for (auto endpoint : children) {
if (endpoint->getInput().device != UserInputMapper::STANDARD_DEVICE) {
standard = false;
break;
}
}
if (standard) {
this->_input = INVALID_STANDARD_INPUT;
}
}
virtual float value() override {
float result = 0;
for (auto& child : _children) {
float childResult = child->value();
if (childResult != 0.0f) {
result = childResult;
}
}
return result;
}
virtual void apply(float newValue, float oldValue, const Endpoint::Pointer& source) override {
qFatal("AnyEndpoint is read only");
}
// AnyEndpoint is used for reading, so return false if any child returns false (has been written to)
virtual bool writeable() const override {
for (auto& child : _children) {
if (!child->writeable()) {
return false;
}
}
return true;
}
virtual bool readable() const override {
for (auto& child : _children) {
if (!child->readable()) {
return false;
}
}
return true;
}
private:
Endpoint::List _children;
};
class InputEndpoint : public Endpoint {
public:
InputEndpoint(const Input& id = Input::INVALID_INPUT)
: Endpoint(id) {
}
virtual float value() override {
_read = true;
if (isPose()) {
return pose().valid ? 1.0f : 0.0f;
}
auto userInputMapper = DependencyManager::get<UserInputMapper>();
auto deviceProxy = userInputMapper->getDeviceProxy(_input);
if (!deviceProxy) {
return 0.0f;
}
return deviceProxy->getValue(_input, 0);
}
// FIXME need support for writing back to vibration / force feedback effects
virtual void apply(float newValue, float oldValue, const Pointer& source) override {}
virtual Pose pose() override {
_read = true;
if (!isPose()) {
return Pose();
}
auto userInputMapper = DependencyManager::get<UserInputMapper>();
auto deviceProxy = userInputMapper->getDeviceProxy(_input);
if (!deviceProxy) {
return Pose();
}
return deviceProxy->getPose(_input, 0);
}
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override { }
virtual bool writeable() const { return false; }
virtual bool readable() const { return !_read; }
virtual void reset() { _read = false; }
private:
bool _read { false };
};
class ActionEndpoint : public Endpoint {
public:
ActionEndpoint(const Input& id = Input::INVALID_INPUT)
: Endpoint(id) {
}
virtual float value() override { return _currentValue; }
virtual void apply(float newValue, float oldValue, const Pointer& source) override {
_currentValue += newValue;
if (_input != Input::INVALID_INPUT) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
userInputMapper->deltaActionState(Action(_input.getChannel()), newValue);
}
}
virtual Pose pose() override { return _currentPose; }
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override {
_currentPose = newValue;
if (!_currentPose.isValid()) {
return;
}
if (_input != Input::INVALID_INPUT) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
userInputMapper->setActionState(Action(_input.getChannel()), _currentPose);
}
}
virtual void reset() override {
_currentValue = 0.0f;
_currentPose = Pose();
}
private:
float _currentValue{ 0.0f };
Pose _currentPose{};
};
UserInputMapper::~UserInputMapper() {
}
@ -856,6 +575,21 @@ Endpoint::Pointer UserInputMapper::endpointFor(const QScriptValue& endpoint) {
return result;
}
if (endpoint.isArray()) {
int length = endpoint.property("length").toInteger();
Endpoint::List children;
for (int i = 0; i < length; i++) {
QScriptValue arrayItem = endpoint.property(i);
Endpoint::Pointer destination = endpointFor(arrayItem);
if (!destination) {
return Endpoint::Pointer();
}
children.push_back(destination);
}
return std::make_shared<AnyEndpoint>(children);
}
qWarning() << "Unsupported input type " << endpoint.toString();
return Endpoint::Pointer();
}
@ -1004,32 +738,46 @@ Endpoint::Pointer UserInputMapper::parseEndpoint(const QJsonValue& value) {
return result;
}
class AndConditional : public Conditional {
public:
using Pointer = std::shared_ptr<AndConditional>;
AndConditional(Conditional::List children) : _children(children) { }
Conditional::Pointer UserInputMapper::conditionalFor(const QJSValue& condition) {
return Conditional::Pointer();
}
virtual bool satisfied() override {
for (auto& conditional : _children) {
if (!conditional->satisfied()) {
return false;
Conditional::Pointer UserInputMapper::conditionalFor(const QScriptValue& condition) {
if (condition.isArray()) {
int length = condition.property("length").toInteger();
Conditional::List children;
for (int i = 0; i < length; i++) {
Conditional::Pointer destination = conditionalFor(condition.property(i));
if (!destination) {
return Conditional::Pointer();
}
children.push_back(destination);
}
return true;
return std::make_shared<AndConditional>(children);
}
private:
Conditional::List _children;
};
if (condition.isNumber()) {
return conditionalFor(Input(condition.toInt32()));
}
class EndpointConditional : public Conditional {
public:
EndpointConditional(Endpoint::Pointer endpoint) : _endpoint(endpoint) {}
virtual bool satisfied() override { return _endpoint && _endpoint->value() != 0.0; }
private:
Endpoint::Pointer _endpoint;
};
if (condition.isFunction()) {
return std::make_shared<ScriptConditional>(condition);
}
qWarning() << "Unsupported conditional type " << condition.toString();
return Conditional::Pointer();
}
Conditional::Pointer UserInputMapper::conditionalFor(const Input& inputId) const {
Locker locker(_lock);
auto iterator = _endpointsByInput.find(inputId);
if (_endpointsByInput.end() == iterator) {
qWarning() << "Unknown input: " << QString::number(inputId.getID(), 16);
return Conditional::Pointer();
}
return std::make_shared<EndpointConditional>(iterator->second);
}
Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value) {
if (value.isArray()) {
@ -1057,7 +805,6 @@ Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value)
return Conditional::parse(value);
}
Filter::Pointer UserInputMapper::parseFilter(const QJsonValue& value) {
Filter::Pointer result;
if (value.isString()) {
@ -1073,7 +820,6 @@ Filter::Pointer UserInputMapper::parseFilter(const QJsonValue& value) {
return result;
}
Filter::List UserInputMapper::parseFilters(const QJsonValue& value) {
if (value.isNull()) {
return Filter::List();
@ -1275,4 +1021,3 @@ void UserInputMapper::disableMapping(const Mapping::Pointer& mapping) {
}
#include "UserInputMapper.moc"

View file

@ -143,6 +143,7 @@ namespace controller {
friend class MappingBuilderProxy;
void runMappings();
static void applyRoutes(const RouteList& route);
static bool applyRoute(const RoutePointer& route, bool force = false);
void enableMapping(const MappingPointer& mapping);
@ -151,6 +152,9 @@ namespace controller {
EndpointPointer endpointFor(const QScriptValue& endpoint);
EndpointPointer endpointFor(const Input& endpoint) const;
EndpointPointer compositeEndpointFor(EndpointPointer first, EndpointPointer second);
ConditionalPointer conditionalFor(const QJSValue& endpoint);
ConditionalPointer conditionalFor(const QScriptValue& endpoint);
ConditionalPointer conditionalFor(const Input& endpoint) const;
MappingPointer parseMapping(const QJsonValue& json);
RoutePointer parseRoute(const QJsonValue& value);

View file

@ -28,6 +28,7 @@ namespace controller {
using Pointer = std::shared_ptr<Conditional>;
using List = std::list<Pointer>;
using Factory = hifi::SimpleFactory<Conditional, QString>;
using Lambda = std::function<bool()>;
virtual bool satisfied() = 0;
virtual bool parseParameters(const QJsonValue& parameters) { return true; }

View file

@ -16,8 +16,8 @@
#include <QtCore/QObject>
#include "Input.h"
#include "Pose.h"
#include "../Input.h"
#include "../Pose.h"
class QScriptValue;

View file

@ -12,8 +12,8 @@
#include <QtCore/QObject>
#include <QtCore/QString>
#include "../Mapping.h"
#include "../Endpoint.h"
#include "Mapping.h"
#include "Endpoint.h"
class QJSValue;
class QScriptValue;

View file

@ -33,7 +33,6 @@ void RouteBuilderProxy::to(const QScriptValue& destination) {
}
void RouteBuilderProxy::to(const Endpoint::Pointer& destination) {
auto sourceEndpoint = _route->source;
_route->destination = destination;
_mapping->routes.push_back(_route);
deleteLater();
@ -56,6 +55,17 @@ QObject* RouteBuilderProxy::filterQml(const QJSValue& expression) {
return this;
}
QObject* RouteBuilderProxy::when(const QScriptValue& expression) {
_route->conditional = _parent.conditionalFor(expression);
return this;
}
QObject* RouteBuilderProxy::whenQml(const QJSValue& expression) {
_route->conditional = _parent.conditionalFor(expression);
return this;
}
QObject* RouteBuilderProxy::filter(const QScriptValue& expression) {
return this;
}

View file

@ -11,10 +11,12 @@
#include <QtCore/QObject>
#include "../Filter.h"
#include "../Route.h"
#include "../Mapping.h"
#include "Filter.h"
#include "Route.h"
#include "Mapping.h"
#include "../UserInputMapper.h"
class QJSValue;
class QScriptValue;
class QJsonValue;
@ -33,9 +35,11 @@ class RouteBuilderProxy : public QObject {
Q_INVOKABLE void toQml(const QJSValue& destination);
Q_INVOKABLE QObject* filterQml(const QJSValue& expression);
Q_INVOKABLE QObject* whenQml(const QJSValue& expression);
Q_INVOKABLE void to(const QScriptValue& destination);
Q_INVOKABLE QObject* debug(bool enable = true);
Q_INVOKABLE QObject* when(const QScriptValue& expression);
Q_INVOKABLE QObject* filter(const QScriptValue& expression);
Q_INVOKABLE QObject* clamp(float min, float max);
Q_INVOKABLE QObject* pulse(float interval);
@ -47,6 +51,7 @@ class RouteBuilderProxy : public QObject {
private:
void to(const Endpoint::Pointer& destination);
void conditional(const Conditional::Pointer& conditional);
void addFilter(Filter::Lambda lambda);
void addFilter(Filter::Pointer filter);
UserInputMapper& _parent;

View file

@ -0,0 +1,21 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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 "AndConditional.h"
using namespace controller;
bool AndConditional::satisfied() {
for (auto& conditional : _children) {
if (!conditional->satisfied()) {
return false;
}
}
return true;
}

View file

@ -0,0 +1,31 @@
//
// Created by Bradley Austin Davis 2015/10/20
// 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_AndConditional_h
#define hifi_Controllers_AndConditional_h
#include "../Conditional.h"
namespace controller {
class AndConditional : public Conditional {
public:
using Pointer = std::shared_ptr<AndConditional>;
AndConditional(Conditional::List children) : _children(children) { }
virtual bool satisfied() override;
private:
Conditional::List _children;
};
}
#endif

View file

@ -0,0 +1,9 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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 "EndpointConditional.h"

View file

@ -0,0 +1,27 @@
//
// Created by Bradley Austin Davis 2015/10/20
// 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_EndpointConditional_h
#define hifi_Controllers_EndpointConditional_h
#include "../Conditional.h"
#include "../Endpoint.h"
namespace controller {
class EndpointConditional : public Conditional {
public:
EndpointConditional(Endpoint::Pointer endpoint) : _endpoint(endpoint) {}
virtual bool satisfied() override { return _endpoint && _endpoint->value() != 0.0; }
private:
Endpoint::Pointer _endpoint;
};
}
#endif

View file

@ -0,0 +1,9 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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 "NotConditional.h"

View file

@ -0,0 +1,16 @@
//
// Created by Bradley Austin Davis 2015/10/20
// 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_NotConditional_h
#define hifi_Controllers_NotConditional_h
#include "../Conditional.h"
#endif

View file

@ -0,0 +1,27 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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 "ScriptConditional.h"
#include <QtCore/QThread>
using namespace controller;
bool ScriptConditional::satisfied() {
updateValue();
return _lastValue;
}
void ScriptConditional::updateValue() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "updateValue", Qt::QueuedConnection);
return;
}
_lastValue = _callable.call().toBool();
}

View file

@ -0,0 +1,34 @@
//
// Created by Bradley Austin Davis 2015/10/20
// 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_ScriptConditional_h
#define hifi_Controllers_ScriptConditional_h
#include <QtCore/QObject>
#include <QtScript/QScriptValue>
#include "../Conditional.h"
namespace controller {
class ScriptConditional : public QObject, public Conditional {
Q_OBJECT;
public:
ScriptConditional(const QScriptValue& callable) : _callable(callable) { }
virtual bool satisfied() override;
protected:
Q_INVOKABLE void updateValue();
private:
QScriptValue _callable;
bool _lastValue { false };
};
}
#endif

View file

@ -0,0 +1,40 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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 "ActionEndpoint.h"
#include <DependencyManager.h>
#include "../../UserInputMapper.h"
using namespace controller;
void ActionEndpoint::apply(float newValue, float oldValue, const Pointer& source) {
_currentValue += newValue;
if (_input != Input::INVALID_INPUT) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
userInputMapper->deltaActionState(Action(_input.getChannel()), newValue);
}
}
void ActionEndpoint::apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) {
_currentPose = newValue;
if (!_currentPose.isValid()) {
return;
}
if (_input != Input::INVALID_INPUT) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
userInputMapper->setActionState(Action(_input.getChannel()), _currentPose);
}
}
void ActionEndpoint::reset() {
_currentValue = 0.0f;
_currentPose = Pose();
}

View file

@ -0,0 +1,41 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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_ActionEndpoint_h
#define hifi_Controllers_ActionEndpoint_h
#include "../Endpoint.h"
#include "../../Actions.h"
#include <DependencyManager.h>
#include "../../UserInputMapper.h"
namespace controller {
class ActionEndpoint : public Endpoint {
public:
ActionEndpoint(const Input& id = Input::INVALID_INPUT) : Endpoint(id) { }
virtual float value() override { return _currentValue; }
virtual void apply(float newValue, float oldValue, const Pointer& source) override;
virtual Pose pose() override { return _currentPose; }
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override;
virtual void reset() override;
private:
float _currentValue{ 0.0f };
Pose _currentPose{};
};
}
#endif

View file

@ -0,0 +1,63 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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 "AnyEndpoint.h"
#include "../../UserInputMapper.h"
using namespace controller;
AnyEndpoint::AnyEndpoint(Endpoint::List children) : Endpoint(Input::INVALID_INPUT), _children(children) {
bool standard = true;
// Ensure if we're building a composite of standard devices the composite itself
// is treated as a standard device for rule processing order
for (auto endpoint : children) {
if (endpoint->getInput().device != UserInputMapper::STANDARD_DEVICE) {
standard = false;
break;
}
}
if (standard) {
this->_input.device = UserInputMapper::STANDARD_DEVICE;
}
}
float AnyEndpoint::value() {
float result = 0;
for (auto& child : _children) {
float childResult = child->value();
if (childResult != 0.0f) {
result = childResult;
}
}
return result;
}
void AnyEndpoint::apply(float newValue, float oldValue, const Endpoint::Pointer& source) {
qFatal("AnyEndpoint is read only");
}
// AnyEndpoint is used for reading, so return false if any child returns false (has been written to)
bool AnyEndpoint::writeable() const {
for (auto& child : _children) {
if (!child->writeable()) {
return false;
}
}
return true;
}
bool AnyEndpoint::readable() const {
for (auto& child : _children) {
if (!child->readable()) {
return false;
}
}
return true;
}

View file

@ -0,0 +1,32 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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_AnyEndpoint_h
#define hifi_Controllers_AnyEndpoint_h
#include "../Endpoint.h"
namespace controller {
class AnyEndpoint : public Endpoint {
friend class UserInputMapper;
public:
AnyEndpoint(Endpoint::List children);
virtual float value() override;
virtual void apply(float newValue, float oldValue, const Endpoint::Pointer& source) override;
virtual bool writeable() const override;
virtual bool readable() const override;
private:
Endpoint::List _children;
};
}
#endif

View file

@ -0,0 +1,9 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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 "ArrayEndpoint.h"

View file

@ -0,0 +1,43 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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_ArrayEndpoint_h
#define hifi_Controllers_ArrayEndpoint_h
#include "../Endpoint.h"
namespace controller {
class ArrayEndpoint : public Endpoint {
friend class UserInputMapper;
public:
using Pointer = std::shared_ptr<ArrayEndpoint>;
ArrayEndpoint() : Endpoint(Input::INVALID_INPUT) { }
virtual float value() override {
return 0.0;
}
virtual void apply(float newValue, float oldValue, const Endpoint::Pointer& source) override {
for (auto& child : _children) {
if (child->writeable()) {
child->apply(newValue, oldValue, source);
}
}
}
virtual bool readable() const override { return false; }
private:
Endpoint::List _children;
};
}
#endif

View file

@ -0,0 +1,32 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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 "CompositeEndpoint.h"
#include "../../UserInputMapper.h"
namespace controller {
CompositeEndpoint::CompositeEndpoint(Endpoint::Pointer first, Endpoint::Pointer second)
: Endpoint(Input::INVALID_INPUT), Pair(first, second) {
if (first->getInput().device == UserInputMapper::STANDARD_DEVICE &&
second->getInput().device == UserInputMapper::STANDARD_DEVICE) {
this->_input.device = UserInputMapper::STANDARD_DEVICE;
}
}
float CompositeEndpoint::value() {
float result = first->value() * -1.0f + second->value();
return result;
}
void CompositeEndpoint::apply(float newValue, float oldValue, const Pointer& source) {
// Composites are read only
}
}

View file

@ -0,0 +1,26 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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_CompositeEndpoint_h
#define hifi_Controllers_CompositeEndpoint_h
#include "../Endpoint.h"
namespace controller {
class CompositeEndpoint : public Endpoint, Endpoint::Pair {
public:
CompositeEndpoint(Endpoint::Pointer first, Endpoint::Pointer second);
virtual float value() override;
virtual void apply(float newValue, float oldValue, const Pointer& source) override;
};
}
#endif

View file

@ -0,0 +1,41 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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 "InputEndpoint.h"
#include <DependencyManager.h>
#include "../../UserInputMapper.h"
using namespace controller;
float InputEndpoint::value(){
_read = true;
if (isPose()) {
return pose().valid ? 1.0f : 0.0f;
}
auto userInputMapper = DependencyManager::get<UserInputMapper>();
auto deviceProxy = userInputMapper->getDeviceProxy(_input);
if (!deviceProxy) {
return 0.0f;
}
return deviceProxy->getValue(_input, 0);
}
Pose InputEndpoint::pose() {
_read = true;
if (!isPose()) {
return Pose();
}
auto userInputMapper = DependencyManager::get<UserInputMapper>();
auto deviceProxy = userInputMapper->getDeviceProxy(_input);
if (!deviceProxy) {
return Pose();
}
return deviceProxy->getPose(_input, 0);
}

View file

@ -0,0 +1,39 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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_InputEndpoint_h
#define hifi_Controllers_InputEndpoint_h
#include "../Endpoint.h"
namespace controller {
class InputEndpoint : public Endpoint {
public:
InputEndpoint(const Input& id = Input::INVALID_INPUT)
: Endpoint(id) {
}
virtual float value() override;
// FIXME need support for writing back to vibration / force feedback effects
virtual void apply(float newValue, float oldValue, const Pointer& source) override {}
virtual Pose pose() override;
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override { }
virtual bool writeable() const { return false; }
virtual bool readable() const { return !_read; }
virtual void reset() { _read = false; }
private:
bool _read { false };
};
}
#endif

View file

@ -0,0 +1,9 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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 "JSEndpoint.h"

View file

@ -0,0 +1,41 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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_JSEndpoint_h
#define hifi_Controllers_JSEndpoint_h
#include "../Endpoint.h"
#include <QtQml/QJSValue>
#include <QtQml/QJSValueList>
namespace controller {
class JSEndpoint : public Endpoint {
public:
JSEndpoint(const QJSValue& callable)
: Endpoint(Input::INVALID_INPUT), _callable(callable) {
}
virtual float value() {
float result = (float)_callable.call().toNumber();
return result;
}
virtual void apply(float newValue, float oldValue, const Pointer& source) {
_callable.call(QJSValueList({ QJSValue(newValue) }));
}
private:
QJSValue _callable;
};
}
#endif

View file

@ -0,0 +1,43 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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 "ScriptEndpoint.h"
#include <QtCore/QThread>
using namespace controller;
float ScriptEndpoint::value() {
updateValue();
return _lastValue;
}
void ScriptEndpoint::updateValue() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "updateValue", Qt::QueuedConnection);
return;
}
_lastValue = (float)_callable.call().toNumber();
}
void ScriptEndpoint::apply(float newValue, float oldValue, const Pointer& source) {
internalApply(newValue, oldValue, source->getInput().getID());
}
void ScriptEndpoint::internalApply(float newValue, float oldValue, int sourceID) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "internalApply", Qt::QueuedConnection,
Q_ARG(float, newValue),
Q_ARG(float, oldValue),
Q_ARG(int, sourceID));
return;
}
_callable.call(QScriptValue(),
QScriptValueList({ QScriptValue(newValue), QScriptValue(oldValue), QScriptValue(sourceID) }));
}

View file

@ -0,0 +1,39 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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_ScriptEndpoint_h
#define hifi_Controllers_ScriptEndpoint_h
#include <QtScript/QScriptValue>
#include "../Endpoint.h"
namespace controller {
class ScriptEndpoint : public Endpoint {
Q_OBJECT;
public:
ScriptEndpoint(const QScriptValue& callable)
: Endpoint(Input::INVALID_INPUT), _callable(callable) {
}
virtual float value();
virtual void apply(float newValue, float oldValue, const Pointer& source);
protected:
Q_INVOKABLE void updateValue();
Q_INVOKABLE virtual void internalApply(float newValue, float oldValue, int sourceID);
private:
QScriptValue _callable;
float _lastValue = 0.0f;
};
}
#endif

View file

@ -0,0 +1,9 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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 "StandardEndpoint.h"

View file

@ -0,0 +1,60 @@
//
// Created by Bradley Austin Davis 2015/10/23
// 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_StandardEndpoint_h
#define hifi_Controllers_StandardEndpoint_h
#include "../Endpoint.h"
namespace controller {
class StandardEndpoint : public VirtualEndpoint {
public:
StandardEndpoint(const Input& input) : VirtualEndpoint(input) {}
virtual bool writeable() const override { return !_written; }
virtual bool readable() const override { return !_read; }
virtual void reset() override {
apply(0.0f, 0.0f, Endpoint::Pointer());
apply(Pose(), Pose(), Endpoint::Pointer());
_written = _read = false;
}
virtual float value() override {
_read = true;
return VirtualEndpoint::value();
}
virtual void apply(float newValue, float oldValue, const Pointer& source) override {
// For standard endpoints, the first NON-ZERO write counts.
if (newValue != 0.0) {
_written = true;
}
VirtualEndpoint::apply(newValue, oldValue, source);
}
virtual Pose pose() override {
_read = true;
return VirtualEndpoint::pose();
}
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override {
if (newValue != Pose()) {
_written = true;
}
VirtualEndpoint::apply(newValue, oldValue, source);
}
private:
bool _written { false };
bool _read { false };
};
}
#endif