diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 6be672900a..79114b8141 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -9,6 +9,8 @@ { "type": "deadZone", "min": 0.05 } ] }, + { "from": "Vive.LTClick", "to": "Standard.LTClick" }, + { "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" }, { "from": "Vive.LS", "to": "Standard.LS" }, { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, @@ -21,6 +23,8 @@ { "type": "deadZone", "min": 0.05 } ] }, + { "from": "Vive.RTClick", "to": "Standard.RTClick" }, + { "from": "Vive.RightGrip", "to": "Standard.RightGrip" }, { "from": "Vive.RS", "to": "Standard.RS" }, { "from": "Vive.RSTouch", "to": "Standard.RSTouch" }, diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index a9d317d407..02ae5706b7 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -65,6 +65,8 @@ Input::NamedVector StandardController::getAvailableInputs() const { // Triggers makePair(LT, "LT"), makePair(RT, "RT"), + makePair(LT_CLICK, "LTClick"), + makePair(RT_CLICK, "RTClick"), // Finger abstractions makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index 501f97f04b..c21d8a2f6e 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -46,6 +46,7 @@ namespace controller { LS_CENTER, LS_X, LS_Y, + LT_CLICK, RIGHT_PRIMARY_THUMB, RIGHT_SECONDARY_THUMB, @@ -56,6 +57,7 @@ namespace controller { RS_CENTER, RS_X, RS_Y, + RT_CLICK, LEFT_PRIMARY_INDEX, LEFT_SECONDARY_INDEX, diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp index 5ae52893e0..7dedfda3cb 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -26,6 +26,7 @@ #include "filters/InvertFilter.h" #include "filters/PulseFilter.h" #include "filters/ScaleFilter.h" +#include "conditionals/AndConditional.h" using namespace controller; @@ -58,12 +59,22 @@ QObject* RouteBuilderProxy::peek(bool enable) { } QObject* RouteBuilderProxy::when(const QScriptValue& expression) { - _route->conditional = _parent.conditionalFor(expression); + auto newConditional = _parent.conditionalFor(expression); + if (_route->conditional) { + _route->conditional = ConditionalPointer(new AndConditional(_route->conditional, newConditional)); + } else { + _route->conditional = newConditional; + } return this; } QObject* RouteBuilderProxy::whenQml(const QJSValue& expression) { - _route->conditional = _parent.conditionalFor(expression); + auto newConditional = _parent.conditionalFor(expression); + if (_route->conditional) { + _route->conditional = ConditionalPointer(new AndConditional(_route->conditional, newConditional)); + } else { + _route->conditional = newConditional; + } return this; } diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index b3b1b20b2b..225ccf42b2 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -199,11 +199,19 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control _axisStateMap[LX] = inputState.Thumbstick[ovrHand_Left].x; _axisStateMap[LY] = inputState.Thumbstick[ovrHand_Left].y; _axisStateMap[LT] = inputState.IndexTrigger[ovrHand_Left]; + // FIXME add hysteresis? Move to JSON config? + if (inputState.IndexTrigger[ovrHand_Left] > 0.9) { + _buttonPressedMap.insert(LT_CLICK); + } _axisStateMap[LEFT_GRIP] = inputState.HandTrigger[ovrHand_Left]; _axisStateMap[RX] = inputState.Thumbstick[ovrHand_Right].x; _axisStateMap[RY] = inputState.Thumbstick[ovrHand_Right].y; _axisStateMap[RT] = inputState.IndexTrigger[ovrHand_Right]; + // FIXME add hysteresis? Move to JSON config? + if (inputState.IndexTrigger[ovrHand_Right] > 0.9) { + _buttonPressedMap.insert(RT_CLICK); + } _axisStateMap[RIGHT_GRIP] = inputState.HandTrigger[ovrHand_Right]; // Buttons diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 4a515978c3..4fccc624be 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -11,6 +11,9 @@ #include "ViveControllerManager.h" +#include +#include + #include #include #include @@ -22,6 +25,7 @@ #include #include + #include #include @@ -284,7 +288,9 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u vr::VRControllerState_t controllerState = vr::VRControllerState_t(); if (_system->GetControllerState(deviceIndex, &controllerState)) { - + //std::stringstream stream; + //stream << std::hex << controllerState.ulButtonPressed << " " << std::hex << controllerState.ulButtonTouched; + //qDebug() << deviceIndex << " " << stream.str().c_str() << controllerState.rAxis[1].x; // process each button for (uint32_t i = 0; i < vr::k_EButton_Max; ++i) { auto mask = vr::ButtonMaskFromId((vr::EVRButtonId)i); @@ -342,6 +348,11 @@ void ViveControllerManager::InputDevice::handleAxisEvent(float deltaTime, uint32 _axisStateMap[isLeftHand ? LY : RY] = stick.y; } else if (axis == vr::k_EButton_SteamVR_Trigger) { _axisStateMap[isLeftHand ? LT : RT] = x; + // The click feeling on the Vive controller trigger represents a value of *precisely* 1.0, + // so we can expose that as an additional button + if (x >= 1.0f) { + _buttonPressedMap.insert(isLeftHand ? LT_CLICK : RT_CLICK); + } } } @@ -463,10 +474,15 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI makePair(RS_X, "RSX"), makePair(RS_Y, "RSY"), + // triggers makePair(LT, "LT"), makePair(RT, "RT"), + // Trigger clicks + makePair(LT_CLICK, "LTClick"), + makePair(RT_CLICK, "RTClick"), + // low profile side grip button. makePair(LEFT_GRIP, "LeftGrip"), makePair(RIGHT_GRIP, "RightGrip"), diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 94a10798fe..0138e1bc3b 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -29,9 +29,8 @@ var WANT_DEBUG_SEARCH_NAME = null; var SPARK_MODEL_SCALE_FACTOR = 0.75; var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing -var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab -var TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab -var TRIGGER_OFF_VALUE = 0.15; +var TRIGGER_OFF_VALUE = 0.1; +var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab var BUMPER_ON_VALUE = 0.5; @@ -376,6 +375,7 @@ function MyController(hand) { this.entityActivated = false; this.triggerValue = 0; // rolling average of trigger value + this.triggerClicked = false; this.rawTriggerValue = 0; this.rawSecondaryValue = 0; this.rawThumbValue = 0; @@ -835,6 +835,10 @@ function MyController(hand) { _this.rawTriggerValue = value; }; + this.triggerClick = function (value) { + _this.triggerClicked = value; + }; + this.secondaryPress = function (value) { _this.rawSecondaryValue = value; }; @@ -847,7 +851,7 @@ function MyController(hand) { }; this.triggerSmoothedGrab = function () { - return this.triggerValue > TRIGGER_GRAB_VALUE; + return this.triggerClicked; }; this.triggerSmoothedSqueezed = function () { @@ -2225,7 +2229,10 @@ var MAPPING_NAME = "com.highfidelity.handControllerGrab"; var mapping = Controller.newMapping(MAPPING_NAME); mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); +mapping.from([Controller.Standard.RTClick]).peek().to(rightController.triggerClick); + mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); +mapping.from([Controller.Standard.LTClick]).peek().to(leftController.triggerClick); mapping.from([Controller.Standard.RB]).peek().to(rightController.secondaryPress); mapping.from([Controller.Standard.LB]).peek().to(leftController.secondaryPress); diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index cb35414cf8..0b41098eff 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -49,11 +49,15 @@ function Trigger(label) { var that = this; that.label = label; that.TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing - that.TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab - that.TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab - that.TRIGGER_OFF_VALUE = 0.15; + that.TRIGGER_OFF_VALUE = 0.10; + that.TRIGGER_ON_VALUE = that.TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab that.rawTriggerValue = 0; that.triggerValue = 0; // rolling average of trigger value + that.triggerClicked = false; + that.triggerClick = function (value) { + print("Trigger clicked is now " + value); + that.triggerClicked = value; + }; that.triggerPress = function (value) { that.rawTriggerValue = value; }; @@ -64,8 +68,8 @@ function Trigger(label) { (triggerValue * (1.0 - that.TRIGGER_SMOOTH_RATIO)); }; // Current smoothed state, without hysteresis. Answering booleans. - that.triggerSmoothedGrab = function () { - return that.triggerValue > that.TRIGGER_GRAB_VALUE; + that.triggerSmoothedClick = function () { + return that.triggerClicked; }; that.triggerSmoothedSqueezed = function () { return that.triggerValue > that.TRIGGER_ON_VALUE; @@ -81,7 +85,7 @@ function Trigger(label) { that.updateSmoothedTrigger(); // The first two are independent of previous state: - if (that.triggerSmoothedGrab()) { + if (that.triggerSmoothedClick()) { state = 'full'; } else if (that.triggerSmoothedReleased()) { state = null; @@ -365,6 +369,8 @@ Script.scriptEnding.connect(clickMapping.disable); // Gather the trigger data for smoothing. clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress); clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress); +clickMapping.from(Controller.Standard.RTClick).peek().to(rightTrigger.triggerClick); +clickMapping.from(Controller.Standard.LTClick).peek().to(leftTrigger.triggerClick); // Full smoothed trigger is a click. function isPointingAtOverlayStartedNonFullTrigger(trigger) { // true if isPointingAtOverlay AND we were NOT full triggered when we became so. diff --git a/scripts/system/libraries/Trigger.js b/scripts/system/libraries/Trigger.js new file mode 100644 index 0000000000..ffde021f5d --- /dev/null +++ b/scripts/system/libraries/Trigger.js @@ -0,0 +1,87 @@ +"use strict"; + +/*jslint vars: true, plusplus: true*/ +/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print*/ + +Trigger = function(properties) { + properties = properties || {}; + var that = this; + that.label = properties.label || Math.random(); + that.SMOOTH_RATIO = properties.smooth || 0.1; // Time averaging of trigger - 0.0 disables smoothing + that.DEADZONE = properties.deadzone || 0.10; // Once pressed, a trigger must fall below the deadzone to be considered un-pressed once pressed. + that.HYSTERESIS = properties.hystersis || 0.05; // If not pressed, a trigger must go above DEADZONE + HYSTERSIS to be considered pressed + + that.value = 0; + that.pressed = false; + that.clicked = false; + + // Handlers + that.onPress = properties.onPress || function(){ + print("Pressed trigger " + that.label) + }; + that.onRelease = properties.onRelease || function(){ + print("Released trigger " + that.label) + }; + that.onClick = properties.onClick || function(){ + print("Clicked trigger " + that.label) + }; + that.onUnclick = properties.onUnclick || function(){ + print("Unclicked trigger " + that.label) + }; + + // Getters + that.isPressed = function() { + return that.pressed; + } + + that.isClicked = function() { + return that.clicked; + } + + that.getValue = function() { + return that.value; + } + + + // Private values + var controller = properties.controller || Controller.Standard.LT; + var controllerClick = properties.controllerClick || Controller.Standard.LTClick; + that.mapping = Controller.newMapping('com.highfidelity.controller.trigger.' + controller + '-' + controllerClick + '.' + that.label + Math.random()); + Script.scriptEnding.connect(that.mapping.disable); + + // Setup mapping, + that.mapping.from(controller).peek().to(function(value) { + that.value = (that.value * that.SMOOTH_RATIO) + + (value * (1.0 - that.SMOOTH_RATIO)); + + var oldPressed = that.pressed; + if (!that.pressed && that.value >= (that.DEADZONE + that.HYSTERESIS)) { + that.pressed = true; + that.onPress(); + } + + if (that.pressed && that.value < that.HYSTERESIS) { + that.pressed = false; + that.onRelease(); + } + }); + + that.mapping.from(controllerClick).peek().to(function(value){ + if (!that.clicked && value > 0.0) { + that.clicked = true; + that.onClick(); + } + if (that.clicked && value == 0.0) { + that.clicked = false; + that.onUnclick(); + } + }); + + that.enable = function() { + that.mapping.enable(); + } + + that.disable = function() { + that.mapping.disable(); + } +}