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

This commit is contained in:
EdgarPironti 2015-10-22 11:50:45 -07:00
commit 09cdc01599
30 changed files with 925 additions and 590 deletions

View file

@ -245,7 +245,7 @@ function MyController(hand, triggerAction) {
};
this.updateSmoothedTrigger = function() {
var triggerValue = Controller.getActionValue(this.triggerAction);
var triggerValue = Controller.getValue(this.triggerAction);
// smooth out trigger value
this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) +
(triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO));
@ -260,7 +260,7 @@ function MyController(hand, triggerAction) {
};
this.triggerSqueezed = function() {
var triggerValue = Controller.getActionValue(this.triggerAction);
var triggerValue = Controller.getValue(this.triggerAction);
return triggerValue > TRIGGER_ON_VALUE;
};
@ -402,8 +402,9 @@ function MyController(hand, triggerAction) {
this.distanceHolding = function() {
var handControllerPosition = Controller.getSpatialControlPosition(this.palm);
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition;
var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation",
"gravity", "ignoreForCollisions"]);
var now = Date.now();
@ -452,8 +453,9 @@ function MyController(hand, triggerAction) {
}
var handPosition = this.getHandPosition();
var handControllerPosition = Controller.getSpatialControlPosition(this.palm);
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition;
var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation"]);
this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR);
@ -595,7 +597,7 @@ function MyController(hand, triggerAction) {
}
this.currentHandControllerTipPosition = Controller.getSpatialControlPosition(this.tip);
this.currentHandControllerTipPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition;;
this.currentObjectTime = Date.now();
};
@ -613,7 +615,7 @@ function MyController(hand, triggerAction) {
// of it's actual offset, let's try imparting a velocity which is at a fixed radius
// from the palm.
var handControllerPosition = Controller.getSpatialControlPosition(this.tip);
var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition;
var now = Date.now();
var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters
@ -856,8 +858,8 @@ function MyController(hand, triggerAction) {
};
}
var rightController = new MyController(RIGHT_HAND, Controller.findAction("RIGHT_HAND_CLICK"));
var leftController = new MyController(LEFT_HAND, Controller.findAction("LEFT_HAND_CLICK"));
var rightController = new MyController(RIGHT_HAND, Controller.Standard.RT);
var leftController = new MyController(LEFT_HAND, Controller.Standard.LT);
function update() {
rightController.update();

View file

@ -1,4 +1,12 @@
ControllerTest = function() {
var standard = Controller.Standard;
var actions = Controller.Actions;
this.mappingEnabled = false;
this.mapping = Controller.newMapping();
this.mapping.from(standard.RX).to(actions.StepYaw);
this.mapping.enable();
this.mappingEnabled = true;
print("Actions");
for (var prop in Controller.Actions) {
@ -24,6 +32,9 @@ ControllerTest = function() {
}
ControllerTest.prototype.onCleanup = function() {
if (this.mappingEnabled) {
this.mapping.disable();
}
}

View file

@ -1,10 +1,10 @@
{
"name": "Hydra to Standard",
"channels": [
{ "from": "Hydra.LY", "to": "Standard.LY" },
{ "from": "Hydra.LY", "filters": "invert", "to": "Standard.LY" },
{ "from": "Hydra.LX", "to": "Standard.LX" },
{ "from": "Hydra.LT", "to": "Standard.LT" },
{ "from": "Hydra.RY", "to": "Standard.RY" },
{ "from": "Hydra.RY", "filters": "invert", "to": "Standard.RY" },
{ "from": "Hydra.RX", "to": "Standard.RX" },
{ "from": "Hydra.RT", "to": "Standard.RT" },

View file

@ -0,0 +1,43 @@
{
"name": "Standard to Action",
"channels": [
{ "from": "Standard.LY", "to": "Actions.TranslateZ" },
{ "from": "Standard.LX", "to": "Actions.TranslateX" },
{ "from": "Standard.RX", "to": "Actions.Yaw" },
{ "from": "Standard.RY", "to": "Actions.Pitch" },
{
"from": "Standard.DU",
"to": "Actions.LONGITUDINAL_FORWARD",
"filters": [ { "type": "scale", "scale": 0.5 } ]
},
{
"from": "Standard.DD",
"to": "Actions.LONGITUDINAL_BACKWARD",
"filters": [ { "type": "scale", "scale": 0.5 } ]
},
{
"from": "Standard.DR",
"to": "Actions.LATERAL_RIGHT",
"filters": [ { "type": "scale", "scale": 0.5 } ]
},
{
"from": "Standard.DL",
"to": "Actions.LATERAL_LEFT",
"filters": [ { "type": "scale", "scale": 0.5 } ]
},
{ "from": "Standard.Y", "to": "Actions.VERTICAL_UP" },
{ "from": "Standard.X", "to": "Actions.VERTICAL_DOWN" },
{
"from": "Standard.RT",
"to": "Actions.BOOM_IN",
"filters": [ { "type": "scale", "scale": 0.1 } ]
},
{
"from": "Standard.LT",
"to": "Actions.BOOM_OUT",
"filters": [ { "type": "scale", "scale": 0.1 } ]
},
{ "from": "Standard.LeftHand", "to": "Actions.LEFT_HAND" },
{ "from": "Standard.RightHand", "to": "Actions.RIGHT_HAND" }
]
}

View file

@ -5,39 +5,17 @@
{ "from": "Standard.LX", "to": "Actions.TranslateX" },
{ "from": "Standard.RX", "to": "Actions.Yaw" },
{ "from": "Standard.RY", "to": "Actions.Pitch" },
{
"from": "Standard.DU",
"to": "Actions.LONGITUDINAL_FORWARD",
"filters": [ { "type": "scale", "scale": 0.5 } ]
},
{
"from": "Standard.DD",
"to": "Actions.LONGITUDINAL_BACKWARD",
"filters": [ { "type": "scale", "scale": 0.5 } ]
},
{
"from": "Standard.DR",
"to": "Actions.LATERAL_RIGHT",
"filters": [ { "type": "scale", "scale": 0.5 } ]
},
{
"from": "Standard.DL",
"to": "Actions.LATERAL_LEFT",
"filters": [ { "type": "scale", "scale": 0.5 } ]
},
{ "from": "Standard.Y", "to": "Actions.VERTICAL_UP" },
{ "from": "Standard.X", "to": "Actions.VERTICAL_DOWN" },
{
"from": "Standard.RT",
"to": "Actions.BOOM_IN",
"filters": [ { "type": "scale", "scale": 0.1 } ]
},
{
"from": "Standard.LT",
"to": "Actions.BOOM_OUT",
"filters": [ { "type": "scale", "scale": 0.1 } ]
},
{ "from": "Standard.LeftHand", "to": "Actions.LEFT_HAND" },
{ "from": "Standard.RightHand", "to": "Actions.RIGHT_HAND" }
{ "from": [ "Standard.DU", "Standard.DU", "Standard.DU", "Standard.DD" ], "to": "Standard.LeftPrimaryThumb" },
{ "from": "Standard.Back", "to": "Standard.LeftSecondaryThumb" },
{ "from": [ "Standard.A", "Standard.B", "Standard.X", "Standard.Y" ], "to": "Standard.RightPrimaryThumb" },
{ "from": "Standard.Start", "to": "Standard.RightSecondaryThumb" },
{ "from": "Standard.LT", "to": "Actions.LeftHandClick" },
{ "from": "Standard.RT", "to": "Actions.RightHandClick" },
{ "from": "Standard.LeftHand", "to": "Actions.LeftHand" },
{ "from": "Standard.RightHand", "to": "Actions.RightHand" }
]
}

View file

@ -3,12 +3,12 @@ import QtQuick.Controls 1.0
import QtQuick.Layouts 1.0
import QtQuick.Dialogs 1.0
Item {
Rectangle {
id: root
property int size: 64
width: size
height: size
color: 'black'
property int controlId: 0
property real value: 0.5
property int scrollWidth: 1
@ -16,7 +16,7 @@ Item {
property real max: 1.0
property bool log: false
property real range: max - min
property color color: 'blue'
property color lineColor: 'yellow'
property bool bar: false
property real lastHeight: -1
property string label: ""
@ -49,19 +49,21 @@ Item {
Text {
anchors.top: parent.top
text: root.label
color: 'white'
}
Text {
anchors.right: parent.right
anchors.top: parent.top
text: root.max
color: 'white'
}
Text {
anchors.right: parent.right
anchors.bottom: parent.bottom
text: root.min
color: 'white'
}
function scroll() {
@ -92,7 +94,7 @@ Item {
ctx.beginPath();
ctx.lineWidth = 1
ctx.strokeStyle = root.color
ctx.strokeStyle = root.lineColor
ctx.moveTo(canvas.width - root.scrollWidth, root.lastHeight).lineTo(canvas.width, currentHeight)
ctx.stroke()
ctx.restore()

View file

@ -20,8 +20,23 @@ HifiControls.VrDialog {
property var standard: Controller.Standard
property var hydra: null
property var testMapping: null
property bool testMappingEnabled: false
property var xbox: null
function buildMapping() {
testMapping = Controller.newMapping();
testMapping.from(standard.RY).invert().to(actions.Pitch);
//testMapping.makeAxis(standard.LB, standard.RB).to(actions.Yaw);
// Step yaw takes a number of degrees
testMapping.from(standard.LB).invert().scale(15.0).to(actions.StepYaw);
testMapping.from(standard.RB).scale(15.0).to(actions.StepYaw);
testMapping.from(standard.RX).scale(15.0).to(actions.StepYaw);
}
function toggleMapping() {
testMapping.enable(!testMappingEnabled);
testMappingEnabled = !testMappingEnabled;
}
Component.onCompleted: {
enabled = true
@ -49,166 +64,63 @@ HifiControls.VrDialog {
Row {
spacing: 8
Button {
text: "Standard Mapping"
onClicked: {
var mapping = Controller.newMapping("Default");
mapping.from(standard.LX).to(actions.TranslateX);
mapping.from(standard.LY).to(actions.TranslateZ);
mapping.from(standard.RY).to(actions.Pitch);
mapping.from(standard.RX).to(actions.Yaw);
mapping.from(standard.DU).scale(0.5).to(actions.LONGITUDINAL_FORWARD);
mapping.from(standard.DD).scale(0.5).to(actions.LONGITUDINAL_BACKWARD);
mapping.from(standard.DL).scale(0.5).to(actions.LATERAL_LEFT);
mapping.from(standard.DR).scale(0.5).to(actions.LATERAL_RIGHT);
mapping.from(standard.X).to(actions.VERTICAL_DOWN);
mapping.from(standard.Y).to(actions.VERTICAL_UP);
mapping.from(standard.RT).scale(0.1).to(actions.BOOM_IN);
mapping.from(standard.LT).scale(0.1).to(actions.BOOM_OUT);
mapping.from(standard.B).to(actions.ACTION1);
mapping.from(standard.A).to(actions.ACTION2);
mapping.from(standard.RB).to(actions.SHIFT);
mapping.from(standard.Back).to(actions.TOGGLE_MUTE);
mapping.from(standard.Start).to(actions.CONTEXT_MENU);
Controller.enableMapping("Default");
enabled = false;
text = "Standard Built"
}
}
Button {
text: root.xbox ? "XBox Mapping" : "XBox not found"
property bool built: false
enabled: root.xbox && !built
text: !root.testMapping ? "Build Mapping" : (root.testMappingEnabled ? "Disable Mapping" : "Enable Mapping")
onClicked: {
var mapping = Controller.newMapping();
mapping.from(xbox.A).to(standard.A);
mapping.from(xbox.B).to(standard.B);
mapping.from(xbox.X).to(standard.X);
mapping.from(xbox.Y).to(standard.Y);
mapping.from(xbox.Up).to(standard.DU);
mapping.from(xbox.Down).to(standard.DD);
mapping.from(xbox.Left).to(standard.DL);
mapping.from(xbox.Right).to(standard.Right);
mapping.from(xbox.LB).to(standard.LB);
mapping.from(xbox.RB).to(standard.RB);
mapping.from(xbox.LS).to(standard.LS);
mapping.from(xbox.RS).to(standard.RS);
mapping.from(xbox.Start).to(standard.Start);
mapping.from(xbox.Back).to(standard.Back);
mapping.from(xbox.LY).to(standard.LY);
mapping.from(xbox.LX).to(standard.LX);
mapping.from(xbox.RY).to(standard.RY);
mapping.from(xbox.RX).to(standard.RX);
mapping.from(xbox.LT).to(standard.LT);
mapping.from(xbox.RT).to(standard.RT);
mapping.enable();
built = false;
text = "XBox Built"
if (!root.testMapping) {
root.buildMapping()
} else {
root.toggleMapping();
}
}
}
Button {
text: root.hydra ? "Hydra Mapping" : "Hydra Not Found"
property bool built: false
enabled: root.hydra && !built
onClicked: {
var mapping = Controller.newMapping();
mapping.from(hydra.LY).invert().to(standard.LY);
mapping.from(hydra.LX).to(standard.LX);
mapping.from(hydra.RY).invert().to(standard.RY);
mapping.from(hydra.RX).to(standard.RX);
mapping.from(hydra.LT).to(standard.LT);
mapping.from(hydra.RT).to(standard.RT);
mapping.enable();
built = false;
text = "Hydra Built"
}
}
Button {
text: "Test Mapping"
onClicked: {
var mapping = Controller.newMapping();
// Inverting a value
mapping.from(hydra.RY).invert().to(standard.RY);
mapping.from(hydra.RX).to(standard.RX);
mapping.from(hydra.LY).to(standard.LY);
mapping.from(hydra.LX).to(standard.LX);
// Assigning a value from a function
// mapping.from(function() { return Math.sin(Date.now() / 250); }).to(standard.RX);
// Constrainting a value to -1, 0, or 1, with a deadzone
// mapping.from(xbox.LY).deadZone(0.5).constrainToInteger().to(standard.LY);
mapping.makeAxis(standard.LB, standard.RB).to(actions.Yaw);
// mapping.from(actions.Yaw).clamp(0, 1).invert().to(actions.YAW_RIGHT);
// mapping.from(actions.Yaw).clamp(-1, 0).to(actions.YAW_LEFT);
// mapping.modifier(keyboard.Ctrl).scale(2.0)
// mapping.from(keyboard.A).to(actions.TranslateLeft)
// mapping.from(keyboard.A, keyboard.Shift).to(actions.TurnLeft)
// mapping.from(keyboard.A, keyboard.Shift, keyboard.Ctrl).scale(2.0).to(actions.TurnLeft)
// // First loopbacks
// // Then non-loopbacks by constraint level (number of inputs)
// mapping.from(xbox.RX).deadZone(0.2).to(xbox.RX)
// mapping.from(standard.RB, standard.LB, keyboard.Shift).to(actions.TurnLeft)
// mapping.from(keyboard.A, keyboard.Shift).to(actions.TurnLeft)
// mapping.from(keyboard.W).when(keyboard.Shift).to(actions.Forward)
testMapping = mapping;
enabled = false
text = "Built"
}
}
Button {
text: "Enable Mapping"
onClicked: root.testMapping.enable()
}
Button {
text: "Disable Mapping"
onClicked: root.testMapping.disable()
}
Button {
text: "Enable Mapping"
onClicked: print(Controller.getValue(root.xbox.LY));
}
}
Row {
Xbox { device: root.standard; label: "Standard"; width: 360 }
Standard { device: root.standard; label: "Standard"; width: 180 }
}
Row {
spacing: 8
Xbox { device: root.xbox; label: "XBox"; width: 360 }
Hydra { device: root.hydra; width: 360 }
Xbox { device: root.xbox; label: "XBox"; width: 180 }
Hydra { device: root.hydra; width: 180 }
}
Row {
spacing: 8
ScrollingGraph {
controlId: Controller.Actions.Yaw
label: "Yaw"
min: -2.0
max: 2.0
size: 64
}
ScrollingGraph {
controlId: Controller.Actions.YawLeft
label: "Yaw Left"
min: -2.0
max: 2.0
size: 64
}
ScrollingGraph {
controlId: Controller.Actions.YawRight
label: "Yaw Right"
min: -2.0
max: 2.0
size: 64
}
ScrollingGraph {
controlId: Controller.Actions.StepYaw
label: "StepYaw"
min: -2.0
max: 2.0
size: 64
}
}
// Row {
// spacing: 8
// ScrollingGraph {
// controlId: Controller.Actions.Yaw
// label: "Yaw"
// min: -3.0
// max: 3.0
// size: 128
// }
//
// ScrollingGraph {
// controlId: Controller.Actions.YAW_LEFT
// label: "Yaw Left"
// min: -3.0
// max: 3.0
// size: 128
// }
//
// ScrollingGraph {
// controlId: Controller.Actions.YAW_RIGHT
// label: "Yaw Right"
// min: -3.0
// max: 3.0
// size: 128
// }
// }
}
} // dialog

View file

@ -13,7 +13,7 @@ Item {
property color color: 'black'
function update() {
value = Controller.getValue(controlId);
value = controlId ? Controller.getValue(controlId) : 0;
canvas.requestPaint();
}

View file

@ -17,8 +17,8 @@ Item {
function update() {
value = Qt.vector2d(
Controller.getValue(controlIds[0]),
Controller.getValue(controlIds[1])
controlIds[0] ? Controller.getValue(controlIds[0]) : 0,
controlIds[1] ? Controller.getValue(controlIds[1]) : 0
);
if (root.invertY) {
value.y = value.y * -1.0
@ -27,7 +27,7 @@ Item {
}
Timer {
interval: 50; running: true; repeat: true
interval: 50; running: controlIds; repeat: true
onTriggered: root.update()
}

View file

@ -0,0 +1,35 @@
import QtQuick 2.1
import QtQuick.Controls 1.0
import QtQuick.Layouts 1.0
import QtQuick.Dialogs 1.0
import "xbox"
Item {
id: root
property real aspect: 300.0 / 215.0
width: 300
height: width / aspect
property var device
property string label: ""
property real scale: width / 300.0
Xbox {
width: root.width; height: root.height
device: root.device
}
// Left primary
ToggleButton {
x: 0; y: parent.height - height;
controlId: root.device.LeftPrimaryThumb
width: 16 * root.scale; height: 16 * root.scale
}
// Left primary
ToggleButton {
x: parent.width - width; y: parent.height - height;
controlId: root.device.RightPrimaryThumb
width: 16 * root.scale; height: 16 * root.scale
}
}

View file

@ -12,12 +12,14 @@ Item {
property real value: 0
property color color: 'black'
function update() {
value = controlId ? Controller.getValue(controlId) : 0;
canvas.requestPaint();
}
Timer {
interval: 50; running: true; repeat: true
onTriggered: {
root.value = Controller.getValue(root.controlId);
canvas.requestPaint();
}
interval: 50; running: root.controlId; repeat: true
onTriggered: root.update()
}
Canvas {

View file

@ -2718,15 +2718,13 @@ void Application::update(float deltaTime) {
}
// Transfer the user inputs to the driveKeys
// FIXME can we drop drive keys and just have the avatar read the action states directly?
myAvatar->clearDriveKeys();
if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) {
if (!_controllerScriptingInterface->areActionsCaptured()) {
myAvatar->setDriveKeys(FWD, userInputMapper->getActionState(controller::Action::LONGITUDINAL_FORWARD));
myAvatar->setDriveKeys(BACK, userInputMapper->getActionState(controller::Action::LONGITUDINAL_BACKWARD));
myAvatar->setDriveKeys(UP, userInputMapper->getActionState(controller::Action::VERTICAL_UP));
myAvatar->setDriveKeys(DOWN, userInputMapper->getActionState(controller::Action::VERTICAL_DOWN));
myAvatar->setDriveKeys(LEFT, userInputMapper->getActionState(controller::Action::LATERAL_LEFT));
myAvatar->setDriveKeys(RIGHT, userInputMapper->getActionState(controller::Action::LATERAL_RIGHT));
myAvatar->setDriveKeys(TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z));
myAvatar->setDriveKeys(TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y));
myAvatar->setDriveKeys(TRANSLATE_X, userInputMapper->getActionState(controller::Action::TRANSLATE_X));
if (deltaTime > FLT_EPSILON) {
// For rotations what we really want are meausures of "angles per second" (in order to prevent
// fps-dependent spin rates) so we need to scale the units of the controller contribution.
@ -2734,14 +2732,12 @@ void Application::update(float deltaTime) {
// controllers to provide a delta_per_second value rather than a raw delta.)
const float EXPECTED_FRAME_RATE = 60.0f;
float timeFactor = EXPECTED_FRAME_RATE * deltaTime;
myAvatar->setDriveKeys(ROT_UP, userInputMapper->getActionState(controller::Action::PITCH_UP) / timeFactor);
myAvatar->setDriveKeys(ROT_DOWN, userInputMapper->getActionState(controller::Action::PITCH_DOWN) / timeFactor);
myAvatar->setDriveKeys(ROT_LEFT, userInputMapper->getActionState(controller::Action::YAW_LEFT) / timeFactor);
myAvatar->setDriveKeys(ROT_RIGHT, userInputMapper->getActionState(controller::Action::YAW_RIGHT) / timeFactor);
myAvatar->setDriveKeys(PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH) / timeFactor);
myAvatar->setDriveKeys(YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW) / timeFactor);
myAvatar->setDriveKeys(STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW) / timeFactor);
}
}
myAvatar->setDriveKeys(BOOM_IN, userInputMapper->getActionState(controller::Action::BOOM_IN));
myAvatar->setDriveKeys(BOOM_OUT, userInputMapper->getActionState(controller::Action::BOOM_OUT));
myAvatar->setDriveKeys(ZOOM, userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z));
}
controller::Pose leftHand = userInputMapper->getPoseState(controller::Action::LEFT_HAND);
controller::Pose rightHand = userInputMapper->getPoseState(controller::Action::RIGHT_HAND);

View file

@ -43,21 +43,6 @@ static const float BILLBOARD_DISTANCE = 5.56f; // meters
extern const float CHAT_MESSAGE_SCALE;
extern const float CHAT_MESSAGE_HEIGHT;
enum DriveKeys {
FWD = 0,
BACK,
LEFT,
RIGHT,
UP,
DOWN,
ROT_LEFT,
ROT_RIGHT,
ROT_UP,
ROT_DOWN,
BOOM_IN,
BOOM_OUT,
MAX_DRIVE_KEYS
};
enum ScreenTintLayer {
SCREEN_TINT_BEFORE_LANDSCAPE = 0,

View file

@ -53,6 +53,7 @@
using namespace std;
static quint64 COMFORT_MODE_PULSE_TIMING = USECS_PER_SECOND / 2; // turn once per half second
const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f);
const float YAW_SPEED = 150.0f; // degrees/sec
const float PITCH_SPEED = 100.0f; // degrees/sec
@ -240,8 +241,31 @@ void MyAvatar::simulate(float deltaTime) {
{
PerformanceTimer perfTimer("transform");
bool stepAction = false;
// When there are no step values, we zero out the last step pulse.
// This allows a user to do faster snapping by tapping a control
for (int i = STEP_TRANSLATE_X; !stepAction && i <= STEP_YAW; ++i) {
if (_driveKeys[i] != 0.0f) {
stepAction = true;
}
}
quint64 now = usecTimestampNow();
quint64 pulseDeltaTime = now - _lastStepPulse;
if (!stepAction) {
_lastStepPulse = 0;
}
if (stepAction && pulseDeltaTime > COMFORT_MODE_PULSE_TIMING) {
_pulseUpdate = true;
}
updateOrientation(deltaTime);
updatePosition(deltaTime);
if (_pulseUpdate) {
_lastStepPulse = now;
_pulseUpdate = false;
}
}
{
@ -524,6 +548,54 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
}
// FIXME - this is super duper dumb... but this is how master works. When you have
// hydras plugged in, you'll get 4 "palms" but only the number of controllers lifted
// of the base station are considered active. So when you ask for "left" you get the
// first active controller. If you have both controllers held up or just the left, that
// will be correct. But if you lift the right controller, then it will be reported
// as "left"... you also see this in the avatars hands.
const PalmData* MyAvatar::getActivePalm(int palmIndex) const {
const HandData* handData = DependencyManager::get<AvatarManager>()->getMyAvatar()->getHandData();
int numberOfPalms = handData->getNumPalms();
int numberOfActivePalms = 0;
for (int i = 0; i < numberOfPalms; i++) {
auto palm = handData->getPalms()[i];
if (palm.isActive()) {
// if we've reached the requested "active" palm, then we will return it
if (numberOfActivePalms == palmIndex) {
return &handData->getPalms()[i];
}
numberOfActivePalms++;
}
}
return NULL;
}
glm::vec3 MyAvatar::getLeftHandPosition() const {
const int LEFT_HAND = 0;
auto palmData = getActivePalm(LEFT_HAND);
return palmData ? palmData->getPosition() : glm::vec3(0.0f);
}
glm::vec3 MyAvatar::getRightHandPosition() const {
const int RIGHT_HAND = 1;
auto palmData = getActivePalm(RIGHT_HAND);
return palmData ? palmData->getPosition() : glm::vec3(0.0f);
}
glm::vec3 MyAvatar::getLeftHandTipPosition() const {
const int LEFT_HAND = 0;
auto palmData = getActivePalm(LEFT_HAND);
return palmData ? palmData->getTipPosition() : glm::vec3(0.0f);
}
glm::vec3 MyAvatar::getRightHandTipPosition() const {
const int RIGHT_HAND = 1;
auto palmData = getActivePalm(RIGHT_HAND);
return palmData ? palmData->getTipPosition() : glm::vec3(0.0f);
}
// virtual
void MyAvatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
// don't render if we've been asked to disable local rendering
@ -1506,69 +1578,44 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
void MyAvatar::updateOrientation(float deltaTime) {
// Smoothly rotate body with arrow keys
float targetSpeed = 0.0f;
// FIXME - this comfort mode code is a total hack, remove it when we have new input mapping
bool isComfortMode = Menu::getInstance()->isOptionChecked(MenuOption::ComfortMode);
bool isHMDMode = qApp->getAvatarUpdater()->isHMDMode();
if (!isHMDMode || !isComfortMode) {
targetSpeed = (_driveKeys[ROT_LEFT] - _driveKeys[ROT_RIGHT]) * YAW_SPEED;
if (targetSpeed != 0.0f) {
const float ROTATION_RAMP_TIMESCALE = 0.1f;
float blend = deltaTime / ROTATION_RAMP_TIMESCALE;
if (blend > 1.0f) {
blend = 1.0f;
}
_bodyYawDelta = (1.0f - blend) * _bodyYawDelta + blend * targetSpeed;
} else if (_bodyYawDelta != 0.0f) {
// attenuate body rotation speed
const float ROTATION_DECAY_TIMESCALE = 0.05f;
float attenuation = 1.0f - deltaTime / ROTATION_DECAY_TIMESCALE;
if (attenuation < 0.0f) {
attenuation = 0.0f;
}
_bodyYawDelta *= attenuation;
float MINIMUM_ROTATION_RATE = 2.0f;
if (fabsf(_bodyYawDelta) < MINIMUM_ROTATION_RATE) {
_bodyYawDelta = 0.0f;
}
float targetSpeed = _driveKeys[YAW] * YAW_SPEED;
if (targetSpeed != 0.0f) {
const float ROTATION_RAMP_TIMESCALE = 0.1f;
float blend = deltaTime / ROTATION_RAMP_TIMESCALE;
if (blend > 1.0f) {
blend = 1.0f;
}
_bodyYawDelta = (1.0f - blend) * _bodyYawDelta + blend * targetSpeed;
} else if (_bodyYawDelta != 0.0f) {
// attenuate body rotation speed
const float ROTATION_DECAY_TIMESCALE = 0.05f;
float attenuation = 1.0f - deltaTime / ROTATION_DECAY_TIMESCALE;
if (attenuation < 0.0f) {
attenuation = 0.0f;
}
_bodyYawDelta *= attenuation;
// update body orientation by movement inputs
setOrientation(getOrientation() *
glm::quat(glm::radians(glm::vec3(0.0f, _bodyYawDelta * deltaTime, 0.0f))));
} else {
// Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll
// get an instantaneous 15 degree turn. If you keep holding the key down you'll get another
// snap turn every half second.
_bodyYawDelta = 0.0f;
static quint64 lastPulse = 0;
quint64 now = usecTimestampNow();
quint64 COMFORT_MODE_PULSE_TIMING = USECS_PER_SECOND / 2; // turn once per half second
float driveLeft = _driveKeys[ROT_LEFT];
float driveRight= _driveKeys[ROT_RIGHT];
if ((driveLeft != 0.0f || driveRight != 0.0f) && (now - lastPulse > COMFORT_MODE_PULSE_TIMING)) {
lastPulse = now;
const float SNAP_TURN_DELTA = 15.0f; // degrees
float direction = (driveLeft - driveRight) < 0.0f ? -1.0f : 1.0f;
float turnAmount = direction * SNAP_TURN_DELTA;
// update body orientation by movement inputs
setOrientation(getOrientation() *
glm::quat(glm::radians(glm::vec3(0.0f, turnAmount, 0.0f))));
float MINIMUM_ROTATION_RATE = 2.0f;
if (fabsf(_bodyYawDelta) < MINIMUM_ROTATION_RATE) {
_bodyYawDelta = 0.0f;
}
}
getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * deltaTime);
float totalBodyYaw = _bodyYawDelta * deltaTime;
// Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll
// get an instantaneous 15 degree turn. If you keep holding the key down you'll get another
// snap turn every half second.
quint64 now = usecTimestampNow();
if (_driveKeys[STEP_YAW] != 0.0f && now - _lastStepPulse > COMFORT_MODE_PULSE_TIMING) {
totalBodyYaw += _driveKeys[STEP_YAW];
}
// update body orientation by movement inputs
setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
getHead()->setBasePitch(getHead()->getBasePitch() + _driveKeys[PITCH] * PITCH_SPEED * deltaTime);
if (qApp->getAvatarUpdater()->isHMDMode()) {
glm::quat orientation = glm::quat_cast(getSensorToWorldMatrix()) * getHMDSensorOrientation();
@ -1628,14 +1675,18 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
float motorEfficiency = glm::clamp(deltaTime / timescale, 0.0f, 1.0f);
glm::vec3 newLocalVelocity = localVelocity;
float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) +
(fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) +
fabsf(_driveKeys[UP] - _driveKeys[DOWN]);
float stepControllerInput = fabsf(_driveKeys[STEP_TRANSLATE_Z]) + fabsf(_driveKeys[STEP_TRANSLATE_Z]) + fabsf(_driveKeys[STEP_TRANSLATE_Z]);
quint64 now = usecTimestampNow();
// FIXME how do I implement step translation as well?
if (stepControllerInput && now - _lastStepPulse > COMFORT_MODE_PULSE_TIMING) {
}
float keyboardInput = fabsf(_driveKeys[TRANSLATE_Z]) + fabsf(_driveKeys[TRANSLATE_X]) + fabsf(_driveKeys[TRANSLATE_Y]);
if (keyboardInput) {
// Compute keyboard input
glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT;
glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT;
glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP;
glm::vec3 front = (_driveKeys[TRANSLATE_Z]) * IDENTITY_FRONT;
glm::vec3 right = (_driveKeys[TRANSLATE_X]) * IDENTITY_RIGHT;
glm::vec3 up = (_driveKeys[TRANSLATE_Y]) * IDENTITY_UP;
glm::vec3 direction = front + right + up;
float directionLength = glm::length(direction);
@ -1686,7 +1737,7 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
}
}
float boomChange = _driveKeys[BOOM_OUT] - _driveKeys[BOOM_IN];
float boomChange = _driveKeys[ZOOM];
_boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
_boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX);
@ -1935,7 +1986,7 @@ void MyAvatar::clearDriveKeys() {
}
void MyAvatar::relayDriveKeysToCharacterController() {
if (_driveKeys[UP] > 0.0f) {
if (_driveKeys[TRANSLATE_Y] > 0.0f) {
_characterController.jump();
}
}

View file

@ -21,6 +21,20 @@
class ModelItemID;
enum DriveKeys {
TRANSLATE_X = 0,
TRANSLATE_Y,
TRANSLATE_Z,
YAW,
STEP_TRANSLATE_X,
STEP_TRANSLATE_Y,
STEP_TRANSLATE_Z,
STEP_YAW,
PITCH,
ZOOM,
MAX_DRIVE_KEYS
};
enum eyeContactTarget {
LEFT_EYE,
RIGHT_EYE,
@ -34,7 +48,6 @@ enum AudioListenerMode {
};
Q_DECLARE_METATYPE(AudioListenerMode);
class MyAvatar : public Avatar {
Q_OBJECT
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
@ -50,6 +63,12 @@ class MyAvatar : public Avatar {
Q_PROPERTY(AudioListenerMode CUSTOM READ getAudioListenerModeCustom)
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition)
Q_PROPERTY(glm::vec3 rightHandPosition READ getRightHandPosition)
Q_PROPERTY(glm::vec3 leftHandTipPosition READ getLeftHandTipPosition)
Q_PROPERTY(glm::vec3 rightHandTipPosition READ getRightHandTipPosition)
public:
MyAvatar(RigPointer rig);
~MyAvatar();
@ -136,6 +155,11 @@ public:
Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; }
Q_INVOKABLE glm::vec3 getLeftHandPosition() const;
Q_INVOKABLE glm::vec3 getRightHandPosition() const;
Q_INVOKABLE glm::vec3 getLeftHandTipPosition() const;
Q_INVOKABLE glm::vec3 getRightHandTipPosition() const;
AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; }
void updateLookAtTargetAvatar();
void clearLookAtTargetAvatar();
@ -274,6 +298,9 @@ private:
void setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visiblity);
const PalmData* getActivePalm(int palmIndex) const;
// derive avatar body position and orientation from the current HMD Sensor location.
// results are in sensor space
glm::mat4 deriveBodyFromHMDSensor() const;
@ -363,6 +390,8 @@ private:
AtRestDetector _hmdAtRestDetector;
glm::vec3 _lastPosition;
bool _lastIsMoving = false;
quint64 _lastStepPulse = 0;
bool _pulseUpdate { false };
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -33,18 +33,14 @@ ConnexionData::ConnexionData() : InputDevice("ConnexionClient") {}
void ConnexionData::handleAxisEvent() {
_axisStateMap[makeInput(ROTATION_AXIS_Y_POS).getChannel()] = (cc_rotation.y > 0.0f) ? cc_rotation.y / MAX_AXIS : 0.0f;
_axisStateMap[makeInput(ROTATION_AXIS_Y_NEG).getChannel()] = (cc_rotation.y < 0.0f) ? -cc_rotation.y / MAX_AXIS : 0.0f;
_axisStateMap[makeInput(POSITION_AXIS_X_POS).getChannel()] = (cc_position.x > 0.0f) ? cc_position.x / MAX_AXIS : 0.0f;
_axisStateMap[makeInput(POSITION_AXIS_X_NEG).getChannel()] = (cc_position.x < 0.0f) ? -cc_position.x / MAX_AXIS : 0.0f;
_axisStateMap[makeInput(POSITION_AXIS_Y_POS).getChannel()] = (cc_position.y > 0.0f) ? cc_position.y / MAX_AXIS : 0.0f;
_axisStateMap[makeInput(POSITION_AXIS_Y_NEG).getChannel()] = (cc_position.y < 0.0f) ? -cc_position.y / MAX_AXIS : 0.0f;
_axisStateMap[makeInput(POSITION_AXIS_Z_POS).getChannel()] = (cc_position.z > 0.0f) ? cc_position.z / MAX_AXIS : 0.0f;
_axisStateMap[makeInput(POSITION_AXIS_Z_NEG).getChannel()] = (cc_position.z < 0.0f) ? -cc_position.z / MAX_AXIS : 0.0f;
_axisStateMap[makeInput(ROTATION_AXIS_X_POS).getChannel()] = (cc_rotation.x > 0.0f) ? cc_rotation.x / MAX_AXIS : 0.0f;
_axisStateMap[makeInput(ROTATION_AXIS_X_NEG).getChannel()] = (cc_rotation.x < 0.0f) ? -cc_rotation.x / MAX_AXIS : 0.0f;
_axisStateMap[makeInput(ROTATION_AXIS_Z_POS).getChannel()] = (cc_rotation.z > 0.0f) ? cc_rotation.z / MAX_AXIS : 0.0f;
_axisStateMap[makeInput(ROTATION_AXIS_Z_NEG).getChannel()] = (cc_rotation.z < 0.0f) ? -cc_rotation.z / MAX_AXIS : 0.0f;
auto rotation = cc_rotation / MAX_AXIS;
_axisStateMap[ROTATE_X] = rotation.x;
_axisStateMap[ROTATE_Y] = rotation.y;
_axisStateMap[ROTATE_Z] = rotation.z;
auto position = cc_rotation / MAX_AXIS;
_axisStateMap[TRANSLATE_X] = position.x;
_axisStateMap[TRANSLATE_Y] = position.y;
_axisStateMap[TRANSLATE_Z] = position.z;
}
void ConnexionData::setButton(int lastButtonState) {
@ -57,24 +53,18 @@ void ConnexionData::buildDeviceProxy(controller::DeviceProxy::Pointer proxy) {
proxy->getButton = [this](const controller::Input& input, int timestamp) -> bool { return this->getButton(input.getChannel()); };
proxy->getAxis = [this](const controller::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); };
proxy->getAvailabeInputs = [this]() -> QVector<controller::Input::NamedPair> {
QVector<controller::Input::NamedPair> availableInputs;
availableInputs.append(controller::Input::NamedPair(makeInput(BUTTON_1), "Left button"));
availableInputs.append(controller::Input::NamedPair(makeInput(BUTTON_2), "Right button"));
availableInputs.append(controller::Input::NamedPair(makeInput(BUTTON_3), "Both buttons"));
availableInputs.append(controller::Input::NamedPair(makeInput(POSITION_AXIS_Y_NEG), "Move backward"));
availableInputs.append(controller::Input::NamedPair(makeInput(POSITION_AXIS_Y_POS), "Move forward"));
availableInputs.append(controller::Input::NamedPair(makeInput(POSITION_AXIS_X_POS), "Move right"));
availableInputs.append(controller::Input::NamedPair(makeInput(POSITION_AXIS_X_NEG), "Move Left"));
availableInputs.append(controller::Input::NamedPair(makeInput(POSITION_AXIS_Z_POS), "Move up"));
availableInputs.append(controller::Input::NamedPair(makeInput(POSITION_AXIS_Z_NEG), "Move down"));
availableInputs.append(controller::Input::NamedPair(makeInput(ROTATION_AXIS_Y_NEG), "Rotate backward"));
availableInputs.append(controller::Input::NamedPair(makeInput(ROTATION_AXIS_Y_POS), "Rotate forward"));
availableInputs.append(controller::Input::NamedPair(makeInput(ROTATION_AXIS_X_POS), "Rotate right"));
availableInputs.append(controller::Input::NamedPair(makeInput(ROTATION_AXIS_X_NEG), "Rotate left"));
availableInputs.append(controller::Input::NamedPair(makeInput(ROTATION_AXIS_Z_POS), "Rotate up"));
availableInputs.append(controller::Input::NamedPair(makeInput(ROTATION_AXIS_Z_NEG), "Rotate down"));
using namespace controller;
static QVector<controller::Input::NamedPair> availableInputs {
Input::NamedPair(makeInput(BUTTON_1), "LeftButton"),
Input::NamedPair(makeInput(BUTTON_2), "RightButton"),
Input::NamedPair(makeInput(BUTTON_3), "BothButtons"),
Input::NamedPair(makeInput(TRANSLATE_X), "TranslateX"),
Input::NamedPair(makeInput(TRANSLATE_Y), "TranslateY"),
Input::NamedPair(makeInput(TRANSLATE_Z), "TranslateZ"),
Input::NamedPair(makeInput(ROTATE_X), "RotateX"),
Input::NamedPair(makeInput(ROTATE_Y), "RotateY"),
Input::NamedPair(makeInput(ROTATE_Z), "RotateZ"),
};
return availableInputs;
};
}

View file

@ -182,18 +182,12 @@ public:
static ConnexionData& getInstance();
ConnexionData();
enum PositionChannel {
POSITION_AXIS_X_POS = 1,
POSITION_AXIS_X_NEG = 2,
POSITION_AXIS_Y_POS = 3,
POSITION_AXIS_Y_NEG = 4,
POSITION_AXIS_Z_POS = 5,
POSITION_AXIS_Z_NEG = 6,
ROTATION_AXIS_X_POS = 7,
ROTATION_AXIS_X_NEG = 8,
ROTATION_AXIS_Y_POS = 9,
ROTATION_AXIS_Y_NEG = 10,
ROTATION_AXIS_Z_POS = 11,
ROTATION_AXIS_Z_NEG = 12
TRANSLATE_X,
TRANSLATE_Y,
TRANSLATE_Z,
ROTATE_X,
ROTATE_Y,
ROTATE_Z,
};
enum ButtonChannel {

View file

@ -12,6 +12,23 @@
namespace controller {
Input::NamedPair makePair(ChannelType type, Action action, const QString& name) {
auto input = Input(UserInputMapper::ACTIONS_DEVICE, toInt(action), type);
return Input::NamedPair(input, name);
}
Input::NamedPair makeAxisPair(Action action, const QString& name) {
return makePair(ChannelType::AXIS, action, name);
}
Input::NamedPair makeButtonPair(Action action, const QString& name) {
return makePair(ChannelType::BUTTON, action, name);
}
Input::NamedPair makePosePair(Action action, const QString& name) {
return makePair(ChannelType::POSE, action, name);
}
// Device functions
void ActionsDevice::buildDeviceProxy(DeviceProxy::Pointer proxy) {
proxy->_name = _name;
@ -19,33 +36,69 @@ namespace controller {
proxy->getAxis = [this](const Input& input, int timestamp) -> float { return 0; };
proxy->getAvailabeInputs = [this]() -> QVector<Input::NamedPair> {
QVector<Input::NamedPair> availableInputs{
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::LONGITUDINAL_BACKWARD), ChannelType::AXIS), "LONGITUDINAL_BACKWARD"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::LONGITUDINAL_FORWARD), ChannelType::AXIS), "LONGITUDINAL_FORWARD"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::LATERAL_LEFT), ChannelType::AXIS), "LATERAL_LEFT"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::LATERAL_RIGHT), ChannelType::AXIS), "LATERAL_RIGHT"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::VERTICAL_DOWN), ChannelType::AXIS), "VERTICAL_DOWN"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::VERTICAL_UP), ChannelType::AXIS), "VERTICAL_UP"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::YAW_LEFT), ChannelType::AXIS), "YAW_LEFT"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::YAW_RIGHT), ChannelType::AXIS), "YAW_RIGHT"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::PITCH_DOWN), ChannelType::AXIS), "PITCH_DOWN"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::PITCH_UP), ChannelType::AXIS), "PITCH_UP"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::BOOM_IN), ChannelType::AXIS), "BOOM_IN"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::BOOM_OUT), ChannelType::AXIS), "BOOM_OUT"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::LEFT_HAND), ChannelType::POSE), "LEFT_HAND"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::RIGHT_HAND), ChannelType::POSE), "RIGHT_HAND"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::LEFT_HAND_CLICK), ChannelType::BUTTON), "LEFT_HAND_CLICK"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::RIGHT_HAND_CLICK), ChannelType::BUTTON), "RIGHT_HAND_CLICK"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::SHIFT), ChannelType::BUTTON), "SHIFT"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::ACTION1), ChannelType::BUTTON), "ACTION1"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::ACTION2), ChannelType::BUTTON), "ACTION2"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::CONTEXT_MENU), ChannelType::BUTTON), "CONTEXT_MENU"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::TOGGLE_MUTE), ChannelType::AXIS), "TOGGLE_MUTE"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::TRANSLATE_X), ChannelType::AXIS), "TranslateX"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::TRANSLATE_Y), ChannelType::AXIS), "TranslateY"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::TRANSLATE_Z), ChannelType::AXIS), "TranslateZ"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::ROLL), ChannelType::AXIS), "Roll"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::PITCH), ChannelType::AXIS), "Pitch"),
Input::NamedPair(Input(UserInputMapper::ACTIONS_DEVICE, toInt(Action::YAW), ChannelType::AXIS), "Yaw")
makeAxisPair(Action::TRANSLATE_X, "TranslateX"),
makeAxisPair(Action::TRANSLATE_Y, "TranslateY"),
makeAxisPair(Action::TRANSLATE_Z, "TranslateZ"),
makeAxisPair(Action::ROLL, "Roll"),
makeAxisPair(Action::PITCH, "Pitch"),
makeAxisPair(Action::YAW, "Yaw"),
makeAxisPair(Action::STEP_YAW, "StepYaw"),
makeAxisPair(Action::STEP_PITCH, "StepPitch"),
makeAxisPair(Action::STEP_ROLL, "StepRoll"),
makeAxisPair(Action::STEP_TRANSLATE_X, "StepTranslateX"),
makeAxisPair(Action::STEP_TRANSLATE_X, "StepTranslateY"),
makeAxisPair(Action::STEP_TRANSLATE_X, "StepTranslateZ"),
makeAxisPair(Action::LONGITUDINAL_BACKWARD, "Backward"),
makeAxisPair(Action::LONGITUDINAL_FORWARD, "Forward"),
makeAxisPair(Action::LATERAL_LEFT, "StrafeLeft"),
makeAxisPair(Action::LATERAL_RIGHT, "StrafeRight"),
makeAxisPair(Action::VERTICAL_DOWN, "Down"),
makeAxisPair(Action::VERTICAL_UP, "Up"),
makeAxisPair(Action::YAW_LEFT, "YawLeft"),
makeAxisPair(Action::YAW_RIGHT, "YawRight"),
makeAxisPair(Action::PITCH_DOWN, "PitchDown"),
makeAxisPair(Action::PITCH_UP, "PitchUp"),
makeAxisPair(Action::BOOM_IN, "BoomIn"),
makeAxisPair(Action::BOOM_OUT, "BoomOut"),
makePosePair(Action::LEFT_HAND, "LeftHand"),
makePosePair(Action::RIGHT_HAND, "RightHand"),
makeButtonPair(Action::LEFT_HAND_CLICK, "LeftHandClick"),
makeButtonPair(Action::RIGHT_HAND_CLICK, "RightHandClick"),
makeButtonPair(Action::SHIFT, "Shift"),
makeButtonPair(Action::ACTION1, "PrimaryAction"),
makeButtonPair(Action::ACTION2, "SecondaryAction"),
makeButtonPair(Action::CONTEXT_MENU, "ContextMenu"),
makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"),
// Deprecated aliases
// FIXME remove after we port all scripts
makeAxisPair(Action::LONGITUDINAL_BACKWARD, "LONGITUDINAL_BACKWARD"),
makeAxisPair(Action::LONGITUDINAL_FORWARD, "LONGITUDINAL_FORWARD"),
makeAxisPair(Action::LATERAL_LEFT, "LATERAL_LEFT"),
makeAxisPair(Action::LATERAL_RIGHT, "LATERAL_RIGHT"),
makeAxisPair(Action::VERTICAL_DOWN, "VERTICAL_DOWN"),
makeAxisPair(Action::VERTICAL_UP, "VERTICAL_UP"),
makeAxisPair(Action::YAW_LEFT, "YAW_LEFT"),
makeAxisPair(Action::YAW_RIGHT, "YAW_RIGHT"),
makeAxisPair(Action::PITCH_DOWN, "PITCH_DOWN"),
makeAxisPair(Action::PITCH_UP, "PITCH_UP"),
makeAxisPair(Action::BOOM_IN, "BOOM_IN"),
makeAxisPair(Action::BOOM_OUT, "BOOM_OUT"),
makePosePair(Action::LEFT_HAND, "LEFT_HAND"),
makePosePair(Action::RIGHT_HAND, "RIGHT_HAND"),
makeButtonPair(Action::LEFT_HAND_CLICK, "LEFT_HAND_CLICK"),
makeButtonPair(Action::RIGHT_HAND_CLICK, "RIGHT_HAND_CLICK"),
makeButtonPair(Action::SHIFT, "SHIFT"),
makeButtonPair(Action::ACTION1, "ACTION1"),
makeButtonPair(Action::ACTION2, "ACTION2"),
makeButtonPair(Action::CONTEXT_MENU, "CONTEXT_MENU"),
makeButtonPair(Action::TOGGLE_MUTE, "TOGGLE_MUTE"),
};
return availableInputs;
};

View file

@ -27,6 +27,16 @@ enum class Action {
ROTATE_Y, YAW = ROTATE_Y,
ROTATE_Z, ROLL = ROTATE_Z,
STEP_YAW,
// FIXME does this have a use case?
STEP_PITCH,
// FIXME does this have a use case?
STEP_ROLL,
STEP_TRANSLATE_X,
STEP_TRANSLATE_Y,
STEP_TRANSLATE_Z,
TRANSLATE_CAMERA_Z,
NUM_COMBINED_AXES,

View file

@ -18,14 +18,4 @@ namespace controller {
return Conditional::Pointer();
}
bool EndpointConditional::satisfied() {
if (!_endpoint) {
return false;
}
auto value = _endpoint->value();
if (value == 0.0f) {
return false;
}
return true;
}
}

View file

@ -17,8 +17,6 @@
#include <shared/Factory.h>
#include "Endpoint.h"
class QJsonValue;
namespace controller {
@ -41,14 +39,6 @@ namespace controller {
static Factory _factory;
};
class EndpointConditional : public Conditional {
public:
EndpointConditional(Endpoint::Pointer endpoint) : _endpoint(endpoint) { }
virtual bool satisfied() override;
private:
Endpoint::Pointer _endpoint;
};
}
#define REGISTER_CONDITIONAL_CLASS(classEntry) \

View file

@ -40,9 +40,12 @@ namespace controller {
virtual void apply(float newValue, float oldValue, const Pointer& source) = 0;
virtual Pose pose() { return Pose(); }
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) {}
virtual const bool isPose() { return _input.isPose(); }
virtual bool writeable() const { return true; }
virtual bool readable() const { return true; }
virtual void reset() { }
const Input& getInput() { return _input; }
protected:
@ -61,6 +64,26 @@ namespace controller {
ReadLambda _readLambda;
WriteLambda _writeLambda;
};
class VirtualEndpoint : public Endpoint {
public:
VirtualEndpoint(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; }
virtual Pose pose() override { return _currentPose; }
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override {
_currentPose = newValue;
}
protected:
float _currentValue { 0.0f };
Pose _currentPose {};
};
}
#endif

View file

@ -55,6 +55,7 @@ struct Input {
Input(const Input& src) : id(src.id) {}
Input& operator = (const Input& src) { id = src.id; return (*this); }
bool operator ==(const Input& right) const { return INVALID_INPUT.id != id && INVALID_INPUT.id != right.id && id == right.id; }
bool operator !=(const Input& right) const { return !(*this == right); }
bool operator < (const Input& src) const { return id < src.id; }
static const Input INVALID_INPUT;

View file

@ -33,6 +33,7 @@ namespace controller {
Pose(const Pose&) = default;
Pose& operator = (const Pose&) = default;
bool operator ==(const Pose& right) const;
bool operator !=(const Pose& right) const { return !(*this == right); }
bool isValid() const { return valid; }
vec3 getTranslation() const { return translation; }
quat getRotation() const { return rotation; }

View file

@ -95,10 +95,10 @@ void StandardController::buildDeviceProxy(DeviceProxy::Pointer proxy) {
availableInputs.append(makePair(DR, "Right"));
availableInputs.append(makePair(LeftPrimaryThumb, "LeftPrimaryThumb"));
availableInputs.append(makePair(LeftSecondaryThumb, "LeftSecondaryThumb"));
availableInputs.append(makePair(RightPrimaryThumb, "RightPrimaryThumb"));
availableInputs.append(makePair(RightSecondaryThumb, "RightSecondaryThumb"));
availableInputs.append(makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"));
availableInputs.append(makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"));
availableInputs.append(makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"));
availableInputs.append(makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"));
return availableInputs;
};

View file

@ -37,10 +37,10 @@ namespace controller {
DR,
// These don't map to SDL types
LeftPrimaryThumb,
LeftSecondaryThumb,
RightPrimaryThumb,
RightSecondaryThumb,
LEFT_PRIMARY_THUMB,
LEFT_SECONDARY_THUMB,
RIGHT_PRIMARY_THUMB,
RIGHT_SECONDARY_THUMB,
NUM_STANDARD_BUTTONS
};

View file

@ -29,7 +29,6 @@ namespace controller {
// Default contruct allocate the poutput size with the current hardcoded action channels
controller::UserInputMapper::UserInputMapper() {
_activeMappings.push_back(_defaultMapping);
_standardController = std::make_shared<StandardController>();
registerDevice(new ActionsDevice());
registerDevice(_standardController.get());
@ -55,22 +54,45 @@ private:
float _lastValue = 0.0f;
};
class VirtualEndpoint : public Endpoint {
class StandardEndpoint : public VirtualEndpoint {
public:
VirtualEndpoint(const Input& id = Input::INVALID_INPUT)
: Endpoint(id) {
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 { return _currentValue; }
virtual void apply(float newValue, float oldValue, const Pointer& source) override { _currentValue = newValue; }
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 Pose pose() override { return _currentPose; }
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override {
_currentPose = newValue;
if (newValue != Pose()) {
_written = true;
}
VirtualEndpoint::apply(newValue, oldValue, source);
}
private:
float _currentValue{ 0.0f };
Pose _currentPose{};
bool _written { false };
bool _read { false };
};
@ -136,12 +158,67 @@ public:
virtual void apply(float newValue, float oldValue, const Pointer& source) {
// Composites are read only
}
private:
Endpoint::Pointer _first;
Endpoint::Pointer _second;
};
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(Input::INVALID_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");
}
virtual bool writeable() const override { return false; }
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:
@ -150,40 +227,44 @@ public:
}
virtual float value() override {
_currentValue = 0.0f;
_read = true;
if (isPose()) {
return _currentValue;
return pose().valid ? 1.0f : 0.0f;
}
auto userInputMapper = DependencyManager::get<UserInputMapper>();
auto deviceProxy = userInputMapper->getDeviceProxy(_input);
if (!deviceProxy) {
return _currentValue;
return 0.0f;
}
_currentValue = deviceProxy->getValue(_input, 0);
return _currentValue;
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 {
_currentPose = Pose();
_read = true;
if (!isPose()) {
return _currentPose;
return Pose();
}
auto userInputMapper = DependencyManager::get<UserInputMapper>();
auto deviceProxy = userInputMapper->getDeviceProxy(_input);
if (!deviceProxy) {
return _currentPose;
return Pose();
}
_currentPose = deviceProxy->getPose(_input, 0);
return _currentPose;
return deviceProxy->getPose(_input, 0);
}
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override {
}
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override { }
virtual bool writeable() const { return !_written; }
virtual bool readable() const { return !_read; }
virtual void reset() { _written = _read = false; }
private:
float _currentValue{ 0.0f };
Pose _currentPose{};
bool _written { false };
bool _read { false };
};
class ActionEndpoint : public Endpoint {
@ -194,9 +275,8 @@ public:
virtual float value() override { return _currentValue; }
virtual void apply(float newValue, float oldValue, const Pointer& source) override {
_currentValue += newValue;
if (!(_input == Input::INVALID_INPUT)) {
if (_input != Input::INVALID_INPUT) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
userInputMapper->deltaActionState(Action(_input.getChannel()), newValue);
}
@ -208,12 +288,17 @@ public:
if (!_currentPose.isValid()) {
return;
}
if (!(_input == Input::INVALID_INPUT)) {
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{};
@ -231,6 +316,7 @@ int UserInputMapper::recordDeviceOfType(const QString& deviceName) {
}
void UserInputMapper::registerDevice(InputDevice* device) {
Locker locker(_lock);
if (device->_deviceID == Input::INVALID_DEVICE) {
device->_deviceID = getFreeDeviceID();
}
@ -254,7 +340,7 @@ void UserInputMapper::registerDevice(InputDevice* device) {
}
Endpoint::Pointer endpoint;
if (input.device == STANDARD_DEVICE) {
endpoint = std::make_shared<VirtualEndpoint>(input);
endpoint = std::make_shared<StandardEndpoint>(input);
} else if (input.device == ACTIONS_DEVICE) {
endpoint = std::make_shared<ActionEndpoint>(input);
} else {
@ -268,13 +354,7 @@ void UserInputMapper::registerDevice(InputDevice* device) {
auto mapping = loadMapping(device->getDefaultMappingConfig());
if (mapping) {
_mappingsByDevice[deviceID] = mapping;
auto& defaultRoutes = _defaultMapping->routes;
// New routes for a device get injected IN FRONT of existing routes. Routes
// are processed in order so this ensures that the standard -> action processing
// takes place after all of the hardware -> standard or hardware -> action processing
// because standard -> action is the first set of routes added.
defaultRoutes.insert(defaultRoutes.begin(), mapping->routes.begin(), mapping->routes.end());
enableMapping(mapping);
}
emit hardwareChanged();
@ -282,6 +362,7 @@ void UserInputMapper::registerDevice(InputDevice* device) {
// FIXME remove the associated device mappings
void UserInputMapper::removeDevice(int deviceID) {
Locker locker(_lock);
auto proxyEntry = _registeredDevices.find(deviceID);
if (_registeredDevices.end() == proxyEntry) {
qCWarning(controllers) << "Attempted to remove unknown device " << deviceID;
@ -290,15 +371,7 @@ void UserInputMapper::removeDevice(int deviceID) {
auto proxy = proxyEntry->second;
auto mappingsEntry = _mappingsByDevice.find(deviceID);
if (_mappingsByDevice.end() != mappingsEntry) {
const auto& mapping = mappingsEntry->second;
const auto& deviceRoutes = mapping->routes;
std::set<Route::Pointer> routeSet(deviceRoutes.begin(), deviceRoutes.end());
auto& defaultRoutes = _defaultMapping->routes;
std::remove_if(defaultRoutes.begin(), defaultRoutes.end(), [&](Route::Pointer route)->bool {
return routeSet.count(route) != 0;
});
disableMapping(mappingsEntry->second);
_mappingsByDevice.erase(mappingsEntry);
}
@ -309,6 +382,7 @@ void UserInputMapper::removeDevice(int deviceID) {
DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) {
Locker locker(_lock);
auto device = _registeredDevices.find(input.getDevice());
if (device != _registeredDevices.end()) {
return (device->second);
@ -318,6 +392,7 @@ DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) {
}
QString UserInputMapper::getDeviceName(uint16 deviceID) {
Locker locker(_lock);
if (_registeredDevices.find(deviceID) != _registeredDevices.end()) {
return _registeredDevices[deviceID]->_name;
}
@ -325,6 +400,7 @@ QString UserInputMapper::getDeviceName(uint16 deviceID) {
}
int UserInputMapper::findDevice(QString name) const {
Locker locker(_lock);
for (auto device : _registeredDevices) {
if (device.second->_name == name) {
return device.first;
@ -334,6 +410,7 @@ int UserInputMapper::findDevice(QString name) const {
}
QVector<QString> UserInputMapper::getDeviceNames() {
Locker locker(_lock);
QVector<QString> result;
for (auto device : _registeredDevices) {
QString deviceName = device.second->_name.split(" (")[0];
@ -347,6 +424,7 @@ int UserInputMapper::findAction(const QString& actionName) const {
}
Input UserInputMapper::findDeviceInput(const QString& inputName) const {
Locker locker(_lock);
// Split the full input name as such: deviceName.inputName
auto names = inputName.split('.');
@ -386,6 +464,7 @@ void fixBisectedAxis(float& full, float& negative, float& positive) {
}
void UserInputMapper::update(float deltaTime) {
Locker locker(_lock);
// Reset the axis state for next loop
for (auto& channel : _actionStates) {
channel = 0.0f;
@ -396,20 +475,7 @@ void UserInputMapper::update(float deltaTime) {
}
// Run the mappings code
update();
// Scale all the channel step with the scale
for (auto i = 0; i < toInt(Action::NUM_ACTIONS); i++) {
if (_externalActionStates[i] != 0) {
_actionStates[i] += _externalActionStates[i];
_externalActionStates[i] = 0.0f;
}
if (_externalPoseStates[i].isValid()) {
_poseStates[i] = _externalPoseStates[i];
_externalPoseStates[i] = Pose();
}
}
runMappings();
// merge the bisected and non-bisected axes for now
fixBisectedAxis(_actionStates[toInt(Action::TRANSLATE_X)], _actionStates[toInt(Action::LATERAL_LEFT)], _actionStates[toInt(Action::LATERAL_RIGHT)]);
@ -419,7 +485,6 @@ void UserInputMapper::update(float deltaTime) {
fixBisectedAxis(_actionStates[toInt(Action::ROTATE_Y)], _actionStates[toInt(Action::YAW_LEFT)], _actionStates[toInt(Action::YAW_RIGHT)]);
fixBisectedAxis(_actionStates[toInt(Action::ROTATE_X)], _actionStates[toInt(Action::PITCH_UP)], _actionStates[toInt(Action::PITCH_DOWN)]);
static const float EPSILON = 0.01f;
for (auto i = 0; i < toInt(Action::NUM_ACTIONS); i++) {
_actionStates[i] *= _actionScales[i];
@ -433,11 +498,13 @@ void UserInputMapper::update(float deltaTime) {
}
Input::NamedVector UserInputMapper::getAvailableInputs(uint16 deviceID) const {
Locker locker(_lock);
auto iterator = _registeredDevices.find(deviceID);
return iterator->second->getAvailabeInputs();
}
QVector<Action> UserInputMapper::getAllActions() const {
Locker locker(_lock);
QVector<Action> actions;
for (auto i = 0; i < toInt(Action::NUM_ACTIONS); i++) {
actions.append(Action(i));
@ -446,6 +513,7 @@ QVector<Action> UserInputMapper::getAllActions() const {
}
QString UserInputMapper::getActionName(Action action) const {
Locker locker(_lock);
for (auto actionPair : getActionInputs()) {
if (actionPair.first.channel == toInt(action)) {
return actionPair.second;
@ -456,6 +524,7 @@ QString UserInputMapper::getActionName(Action action) const {
QVector<QString> UserInputMapper::getActionNames() const {
Locker locker(_lock);
QVector<QString> result;
for (auto actionPair : getActionInputs()) {
result << actionPair.second;
@ -564,76 +633,95 @@ Input UserInputMapper::makeStandardInput(controller::StandardPoseChannel pose) {
return Input(STANDARD_DEVICE, pose, ChannelType::POSE);
}
void UserInputMapper::update() {
void UserInputMapper::runMappings() {
static auto deviceNames = getDeviceNames();
_overrideValues.clear();
_overrides.clear();
EndpointSet readEndpoints;
EndpointSet writtenEndpoints;
for (auto endpointEntry : this->_endpointsByInput) {
endpointEntry.second->reset();
}
// Now process the current values for each level of the stack
for (auto& mapping : _activeMappings) {
for (const auto& route : mapping->routes) {
const auto& source = route->source;
// Endpoints can only be read once (though a given mapping can route them to
// multiple places). Consider... If the default is to wire the A button to JUMP
// and someone else wires it to CONTEXT_MENU, I don't want both to occur when
// I press the button. The exception is if I'm wiring a control back to itself
// in order to adjust my interface, like inverting the Y axis on an analog stick
if (readEndpoints.count(source)) {
continue;
}
const auto& destination = route->destination;
// THis could happen if the route destination failed to create
// FIXME: Maybe do not create the route if the destination failed and avoid this case ?
if (!destination) {
continue;
}
if (writtenEndpoints.count(destination)) {
continue;
}
if (route->conditional) {
if (!route->conditional->satisfied()) {
continue;
}
}
// Standard controller destinations can only be can only be used once.
if (getStandardDeviceID() == destination->getInput().getDevice()) {
writtenEndpoints.insert(destination);
}
// Only consume the input if the route isn't a loopback.
// This allows mappings like `mapping.from(xbox.RY).invert().to(xbox.RY);`
bool loopback = source == destination;
if (!loopback) {
readEndpoints.insert(source);
}
// Fetch the value, may have been overriden by previous loopback routes
if (source->isPose()) {
Pose value = getPose(source);
// no filters yet for pose
destination->apply(value, Pose(), source);
} else {
// Fetch the value, may have been overriden by previous loopback routes
float value = getValue(source);
// Apply each of the filters.
for (const auto& filter : route->filters) {
value = filter->apply(value);
}
if (loopback) {
_overrideValues[source] = value;
} else {
destination->apply(value, 0, source);
}
}
for (const auto& route : _deviceRoutes) {
if (!route) {
continue;
}
applyRoute(route);
}
for (const auto& route : _standardRoutes) {
if (!route) {
continue;
}
applyRoute(route);
}
}
void UserInputMapper::applyRoute(const Route::Pointer& route) {
if (route->conditional) {
if (!route->conditional->satisfied()) {
return;
}
}
auto source = route->source;
if (_overrides.count(source)) {
source = _overrides[source];
}
// Endpoints can only be read once (though a given mapping can route them to
// multiple places). Consider... If the default is to wire the A button to JUMP
// and someone else wires it to CONTEXT_MENU, I don't want both to occur when
// I press the button. The exception is if I'm wiring a control back to itself
// in order to adjust my interface, like inverting the Y axis on an analog stick
if (!source->readable()) {
return;
}
auto input = source->getInput();
float value = source->value();
if (value != 0.0) {
int i = 0;
}
auto destination = route->destination;
// THis could happen if the route destination failed to create
// FIXME: Maybe do not create the route if the destination failed and avoid this case ?
if (!destination) {
return;
}
// FIXME?, should come before or after the override logic?
if (!destination->writeable()) {
return;
}
// Only consume the input if the route isn't a loopback.
// This allows mappings like `mapping.from(xbox.RY).invert().to(xbox.RY);`
bool loopback = (source->getInput() == destination->getInput()) && (source->getInput() != Input::INVALID_INPUT);
// Each time we loop back we re-write the override
if (loopback) {
_overrides[source] = destination = std::make_shared<StandardEndpoint>(source->getInput());
}
// Fetch the value, may have been overriden by previous loopback routes
if (source->isPose()) {
Pose value = getPose(source);
// no filters yet for pose
destination->apply(value, Pose(), source);
} else {
// Fetch the value, may have been overriden by previous loopback routes
float value = getValue(source);
// Apply each of the filters.
for (const auto& filter : route->filters) {
value = filter->apply(value);
}
destination->apply(value, 0, source);
}
}
@ -666,6 +754,7 @@ Endpoint::Pointer UserInputMapper::endpointFor(const QScriptValue& endpoint) {
}
Endpoint::Pointer UserInputMapper::endpointFor(const Input& inputId) const {
Locker locker(_lock);
auto iterator = _endpointsByInput.find(inputId);
if (_endpointsByInput.end() == iterator) {
qWarning() << "Unknown input: " << QString::number(inputId.getID(), 16);
@ -689,6 +778,7 @@ Endpoint::Pointer UserInputMapper::compositeEndpointFor(Endpoint::Pointer first,
Mapping::Pointer UserInputMapper::newMapping(const QString& mappingName) {
Locker locker(_lock);
if (_mappingsByName.count(mappingName)) {
qCWarning(controllers) << "Refusing to recreate mapping named " << mappingName;
}
@ -721,8 +811,8 @@ Mapping::Pointer UserInputMapper::newMapping(const QString& mappingName) {
// return result;
//}
void UserInputMapper::enableMapping(const QString& mappingName, bool enable) {
Locker locker(_lock);
qCDebug(controllers) << "Attempting to enable mapping " << mappingName;
auto iterator = _mappingsByName.find(mappingName);
if (_mappingsByName.end() == iterator) {
@ -732,21 +822,17 @@ void UserInputMapper::enableMapping(const QString& mappingName, bool enable) {
auto mapping = iterator->second;
if (enable) {
_activeMappings.push_front(mapping);
enableMapping(mapping);
} else {
auto activeIterator = std::find(_activeMappings.begin(), _activeMappings.end(), mapping);
if (_activeMappings.end() == activeIterator) {
qCWarning(controllers) << "Attempted to disable inactive mapping " << mappingName;
return;
}
_activeMappings.erase(activeIterator);
disableMapping(mapping);
}
}
float UserInputMapper::getValue(const Endpoint::Pointer& endpoint) const {
auto valuesIterator = _overrideValues.find(endpoint);
if (_overrideValues.end() != valuesIterator) {
return valuesIterator->second;
Locker locker(_lock);
auto valuesIterator = _overrides.find(endpoint);
if (_overrides.end() != valuesIterator) {
return valuesIterator->second->value();
}
return endpoint->value();
@ -776,6 +862,7 @@ Pose UserInputMapper::getPose(const Input& input) const {
}
Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) {
Locker locker(_lock);
if (jsonFile.isEmpty()) {
return Mapping::Pointer();
}
@ -790,27 +877,70 @@ Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) {
return parseMapping(json);
}
const QString JSON_NAME = QStringLiteral("name");
const QString JSON_CHANNELS = QStringLiteral("channels");
const QString JSON_CHANNEL_FROM = QStringLiteral("from");
const QString JSON_CHANNEL_WHEN = QStringLiteral("when");
const QString JSON_CHANNEL_TO = QStringLiteral("to");
const QString JSON_CHANNEL_FILTERS = QStringLiteral("filters");
static const QString JSON_NAME = QStringLiteral("name");
static const QString JSON_CHANNELS = QStringLiteral("channels");
static const QString JSON_CHANNEL_FROM = QStringLiteral("from");
static const QString JSON_CHANNEL_WHEN = QStringLiteral("when");
static const QString JSON_CHANNEL_TO = QStringLiteral("to");
static const QString JSON_CHANNEL_FILTERS = QStringLiteral("filters");
Endpoint::Pointer UserInputMapper::parseEndpoint(const QJsonValue& value) {
Endpoint::Pointer result;
if (value.isString()) {
auto input = findDeviceInput(value.toString());
return endpointFor(input);
result = endpointFor(input);
} else if (value.isObject()) {
// Endpoint is defined as an object, we expect a js function then
return Endpoint::Pointer();
}
return Endpoint::Pointer();
if (!result) {
qWarning() << "Invalid endpoint definition " << value;
}
return result;
}
class AndConditional : public Conditional {
public:
using Pointer = std::shared_ptr<AndConditional>;
AndConditional(Conditional::List children) : _children(children) { }
virtual bool satisfied() override {
for (auto& conditional : _children) {
if (!conditional->satisfied()) {
return false;
}
}
return true;
}
private:
Conditional::List _children;
};
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;
};
Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value) {
if (value.isString()) {
if (value.isArray()) {
// Support "when" : [ "GamePad.RB", "GamePad.LB" ]
Conditional::List children;
for (auto arrayItem : value.toArray()) {
Conditional::Pointer childConditional = parseConditional(arrayItem);
if (!childConditional) {
return Conditional::Pointer();
}
children.push_back(childConditional);
}
return std::make_shared<AndConditional>(children);
} else if (value.isString()) {
// Support "when" : "GamePad.RB"
auto input = findDeviceInput(value.toString());
auto endpoint = endpointFor(input);
if (!endpoint) {
@ -818,11 +948,85 @@ Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value)
}
return std::make_shared<EndpointConditional>(endpoint);
}
}
return Conditional::parse(value);
}
Filter::Pointer UserInputMapper::parseFilter(const QJsonValue& value) {
Filter::Pointer result;
if (value.isString()) {
result = Filter::getFactory().create(value.toString());
} else if (value.isObject()) {
result = Filter::parse(value.toObject());
}
if (!result) {
qWarning() << "Invalid filter definition " << value;
}
return result;
}
Filter::List UserInputMapper::parseFilters(const QJsonValue& value) {
if (value.isNull()) {
return Filter::List();
}
if (value.isArray()) {
Filter::List result;
auto filtersArray = value.toArray();
for (auto filterValue : filtersArray) {
Filter::Pointer filter = parseFilter(filterValue);
if (!filter) {
return Filter::List();
}
result.push_back(filter);
}
return result;
}
Filter::Pointer filter = parseFilter(value);
if (!filter) {
return Filter::List();
}
return Filter::List({ filter });
}
Endpoint::Pointer UserInputMapper::parseDestination(const QJsonValue& value) {
if (value.isArray()) {
ArrayEndpoint::Pointer result = std::make_shared<ArrayEndpoint>();
for (auto arrayItem : value.toArray()) {
Endpoint::Pointer destination = parseEndpoint(arrayItem);
if (!destination) {
return Endpoint::Pointer();
}
result->_children.push_back(destination);
}
return result;
}
return parseEndpoint(value);
}
Endpoint::Pointer UserInputMapper::parseSource(const QJsonValue& value) {
if (value.isArray()) {
AnyEndpoint::Pointer result = std::make_shared<AnyEndpoint>();
for (auto arrayItem : value.toArray()) {
Endpoint::Pointer destination = parseEndpoint(arrayItem);
if (!destination) {
return Endpoint::Pointer();
}
result->_children.push_back(destination);
}
return result;
}
return parseEndpoint(value);
}
Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) {
if (!value.isObject()) {
return Route::Pointer();
@ -830,47 +1034,37 @@ Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) {
const auto& obj = value.toObject();
Route::Pointer result = std::make_shared<Route>();
result->source = parseEndpoint(obj[JSON_CHANNEL_FROM]);
result->source = parseSource(obj[JSON_CHANNEL_FROM]);
if (!result->source) {
qWarning() << "Invalid route source " << obj[JSON_CHANNEL_FROM];
return Route::Pointer();
}
result->destination = parseEndpoint(obj[JSON_CHANNEL_TO]);
result->destination = parseDestination(obj[JSON_CHANNEL_TO]);
if (!result->destination) {
qWarning() << "Invalid route destination " << obj[JSON_CHANNEL_TO];
return Route::Pointer();
}
if (obj.contains(JSON_CHANNEL_WHEN)) {
auto when = parseConditional(obj[JSON_CHANNEL_WHEN]);
if (!when) {
qWarning() << "Invalid route conditional " << obj[JSON_CHANNEL_TO];
auto conditionalsValue = obj[JSON_CHANNEL_WHEN];
result->conditional = parseConditional(conditionalsValue);
if (!result->conditional) {
qWarning() << "Invalid route conditionals " << conditionalsValue;
return Route::Pointer();
}
result->conditional = when;
}
const auto& filtersValue = obj[JSON_CHANNEL_FILTERS];
// FIXME support strings for filters with no parameters, both in the array and at the top level...
// i.e.
// { "from": "Standard.DU", "to" : "Actions.LONGITUDINAL_FORWARD", "filters" : "invert" },
// and
// { "from": "Standard.DU", "to" : "Actions.LONGITUDINAL_FORWARD", "filters" : [ "invert", "constrainToInteger" ] },
if (filtersValue.isArray()) {
auto filtersArray = filtersValue.toArray();
for (auto filterValue : filtersArray) {
if (!filterValue.isObject()) {
qWarning() << "Invalid filter " << filterValue;
return Route::Pointer();
}
Filter::Pointer filter = Filter::parse(filterValue.toObject());
if (!filter) {
qWarning() << "Invalid filter " << filterValue;
return Route::Pointer();
}
result->filters.push_back(filter);
if (obj.contains(JSON_CHANNEL_FILTERS)) {
auto filtersValue = obj[JSON_CHANNEL_FILTERS];
result->filters = parseFilters(filtersValue);
if (result->filters.empty()) {
qWarning() << "Invalid route filters " << filtersValue;
return Route::Pointer();
}
}
return result;
}
@ -917,6 +1111,36 @@ Mapping::Pointer UserInputMapper::parseMapping(const QString& json) {
return parseMapping(doc.object());
}
void UserInputMapper::enableMapping(const Mapping::Pointer& mapping) {
Locker locker(_lock);
// New routes for a device get injected IN FRONT of existing routes. Routes
// are processed in order so this ensures that the standard -> action processing
// takes place after all of the hardware -> standard or hardware -> action processing
// because standard -> action is the first set of routes added.
for (auto route : mapping->routes) {
if (route->source->getInput().device == STANDARD_DEVICE) {
_standardRoutes.push_front(route);
} else {
_deviceRoutes.push_front(route);
}
}
}
void UserInputMapper::disableMapping(const Mapping::Pointer& mapping) {
Locker locker(_lock);
const auto& deviceRoutes = mapping->routes;
std::set<Route::Pointer> routeSet(deviceRoutes.begin(), deviceRoutes.end());
// FIXME this seems to result in empty route pointers... need to find a better way to remove them.
std::remove_if(_deviceRoutes.begin(), _deviceRoutes.end(), [&](Route::Pointer route)->bool {
return routeSet.count(route) != 0;
});
std::remove_if(_standardRoutes.begin(), _standardRoutes.end(), [&](Route::Pointer route)->bool {
return routeSet.count(route) != 0;
});
}
}
#include "UserInputMapper.moc"

View file

@ -15,6 +15,7 @@
#include <unordered_set>
#include <functional>
#include <memory>
#include <mutex>
#include <QtQml/QJSValue>
#include <QtScript/QScriptValue>
@ -50,7 +51,7 @@ namespace controller {
using MappingStack = std::list<Mapping::Pointer>;
using InputToEndpointMap = std::map<Input, Endpoint::Pointer>;
using EndpointSet = std::unordered_set<Endpoint::Pointer>;
using ValueMap = std::map<Endpoint::Pointer, float>;
using EndpointOverrideMap = std::map<Endpoint::Pointer, Endpoint::Pointer>;
using EndpointPair = std::pair<Endpoint::Pointer, Endpoint::Pointer>;
using EndpointPairMap = std::map<EndpointPair, Endpoint::Pointer>;
using DevicesMap = std::map<int, DeviceProxy::Pointer>;
@ -86,9 +87,9 @@ namespace controller {
int findAction(const QString& actionName) const;
QVector<QString> getActionNames() const;
void setActionState(Action action, float value) { _externalActionStates[toInt(action)] = value; }
void deltaActionState(Action action, float delta) { _externalActionStates[toInt(action)] += delta; }
void setActionState(Action action, const Pose& value) { _externalPoseStates[toInt(action)] = value; }
void setActionState(Action action, float value) { _actionStates[toInt(action)] = value; }
void deltaActionState(Action action, float delta) { _actionStates[toInt(action)] += delta; }
void setActionState(Action action, const Pose& value) { _poseStates[toInt(action)] = value; }
static Input makeStandardInput(controller::StandardButtonChannel button);
static Input makeStandardInput(controller::StandardAxisChannel axis);
@ -119,20 +120,16 @@ namespace controller {
void hardwareChanged();
protected:
virtual void update();
// GetFreeDeviceID should be called before registering a device to use an ID not used by a different device.
uint16 getFreeDeviceID() { return _nextFreeDeviceID++; }
InputDevice::Pointer _standardController;
DevicesMap _registeredDevices;
uint16 _nextFreeDeviceID = STANDARD_DEVICE + 1;
std::vector<float> _actionStates = std::vector<float>(toInt(Action::NUM_ACTIONS), 0.0f);
std::vector<float> _externalActionStates = std::vector<float>(toInt(Action::NUM_ACTIONS), 0.0f);
std::vector<float> _actionScales = std::vector<float>(toInt(Action::NUM_ACTIONS), 1.0f);
std::vector<float> _lastActionStates = std::vector<float>(toInt(Action::NUM_ACTIONS), 0.0f);
std::vector<Pose> _poseStates = std::vector<Pose>(toInt(Action::NUM_ACTIONS));
std::vector<Pose> _externalPoseStates = std::vector<Pose>(toInt(Action::NUM_ACTIONS));
glm::mat4 _sensorToWorldMat;
@ -144,24 +141,40 @@ namespace controller {
friend class RouteBuilderProxy;
friend class MappingBuilderProxy;
void runMappings();
void applyRoute(const Route::Pointer& route);
void enableMapping(const Mapping::Pointer& mapping);
void disableMapping(const Mapping::Pointer& mapping);
Endpoint::Pointer endpointFor(const QJSValue& endpoint);
Endpoint::Pointer endpointFor(const QScriptValue& endpoint);
Endpoint::Pointer endpointFor(const Input& endpoint) const;
Endpoint::Pointer compositeEndpointFor(Endpoint::Pointer first, Endpoint::Pointer second);
Mapping::Pointer parseMapping(const QJsonValue& json);
Route::Pointer parseRoute(const QJsonValue& value);
Conditional::Pointer parseConditional(const QJsonValue& value);
Endpoint::Pointer parseDestination(const QJsonValue& value);
Endpoint::Pointer parseSource(const QJsonValue& value);
Endpoint::Pointer parseEndpoint(const QJsonValue& value);
Conditional::Pointer parseConditional(const QJsonValue& value);
static Filter::Pointer parseFilter(const QJsonValue& value);
static Filter::List parseFilters(const QJsonValue& value);
InputToEndpointMap _endpointsByInput;
EndpointToInputMap _inputsByEndpoint;
EndpointPairMap _compositeEndpoints;
ValueMap _overrideValues;
EndpointOverrideMap _overrides;
MappingNameMap _mappingsByName;
Mapping::Pointer _defaultMapping{ std::make_shared<Mapping>("Default") };
MappingDeviceMap _mappingsByDevice;
MappingStack _activeMappings;
Route::List _deviceRoutes;
Route::List _standardRoutes;
using Locker = std::unique_lock<std::recursive_mutex>;
mutable std::recursive_mutex _lock;
};
}

View file

@ -230,7 +230,7 @@ void SixenseManager::update(float deltaTime, bool jointsCaptured) {
if (!jointsCaptured) {
// Rotation of Palm
glm::quat rotation(data->rot_quat[3], data->rot_quat[0], data->rot_quat[1], data->rot_quat[2]);
handlePoseEvent(position, rotation, numActiveControllers - 1);
handlePoseEvent(position, rotation, left);
} else {
_poseStateMap.clear();
}

View file

@ -414,10 +414,10 @@ void ViveControllerManager::buildDeviceProxy(controller::DeviceProxy::Pointer pr
makePair(LS, "LS"),
makePair(RS, "RS"),
makePair(LEFT_HAND, "LeftHand"),
makePair(RIGHT_HAND, "RightHand"),
};
//availableInputs.append(Input::NamedPair(makeInput(LEFT_HAND), "Left Hand"));
//availableInputs.append(Input::NamedPair(makeInput(BUTTON_A, 0), "Left Button A"));
//availableInputs.append(Input::NamedPair(makeInput(GRIP_BUTTON, 0), "Left Grip Button"));
//availableInputs.append(Input::NamedPair(makeInput(TRACKPAD_BUTTON, 0), "Left Trackpad Button"));