Make hand pointer lasers 'click' on fully pressing and clicking the vive controller

This commit is contained in:
Brad Davis 2016-07-13 16:06:37 -07:00
parent e18d664204
commit 091e798267
9 changed files with 156 additions and 13 deletions

View file

@ -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" },

View file

@ -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"),

View file

@ -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,

View file

@ -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;
}

View file

@ -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

View file

@ -11,6 +11,9 @@
#include "ViveControllerManager.h"
#include <sstream>
#include <QtCore/QDebug>
#include <PerfStat.h>
#include <PathUtils.h>
#include <GeometryCache.h>
@ -22,6 +25,7 @@
#include <UserActivityLogger.h>
#include <OffscreenUi.h>
#include <controllers/UserInputMapper.h>
#include <controllers/StandardControls.h>
@ -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"),

View file

@ -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);

View file

@ -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.

View file

@ -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();
}
}