Merge pull request #8240 from jherico/hand_controller

Menu & hand controller polish
This commit is contained in:
Brad Hefta-Gaub 2016-07-15 12:36:31 -07:00 committed by GitHub
commit 7bb31fe075
15 changed files with 182 additions and 26 deletions

View file

@ -3,9 +3,17 @@
"channels": [
{ "from": "Hydra.LY", "filters": "invert", "to": "Standard.LY" },
{ "from": "Hydra.LX", "to": "Standard.LX" },
{ "from": "Hydra.LT", "to": "Standard.LTClick",
"peek": true,
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
},
{ "from": "Hydra.LT", "to": "Standard.LT" },
{ "from": "Hydra.RY", "filters": "invert", "to": "Standard.RY" },
{ "from": "Hydra.RX", "to": "Standard.RX" },
{ "from": "Hydra.RT", "to": "Standard.RTClick",
"peek": true,
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
},
{ "from": "Hydra.RT", "to": "Standard.RT" },
{ "from": "Hydra.LB", "to": "Standard.LB" },

View file

@ -8,6 +8,10 @@
{ "from": "OculusTouch.LY", "filters": "invert", "to": "Standard.LY" },
{ "from": "OculusTouch.LX", "to": "Standard.LX" },
{ "from": "OculusTouch.LT", "to": "Standard.LTClick",
"peek": true,
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
},
{ "from": "OculusTouch.LT", "to": "Standard.LT" },
{ "from": "OculusTouch.LS", "to": "Standard.LS" },
{ "from": "OculusTouch.LeftGrip", "to": "Standard.LeftGrip" },
@ -15,6 +19,10 @@
{ "from": "OculusTouch.RY", "filters": "invert", "to": "Standard.RY" },
{ "from": "OculusTouch.RX", "to": "Standard.RX" },
{ "from": "OculusTouch.RT", "to": "Standard.RTClick",
"peek": true,
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
},
{ "from": "OculusTouch.RT", "to": "Standard.RT" },
{ "from": "OculusTouch.RS", "to": "Standard.RS" },
{ "from": "OculusTouch.RightGrip", "to": "Standard.RightGrip" },

View file

@ -10,14 +10,7 @@
{ "from": "Standard.RB", "to": "Actions.UiNavGroup" },
{ "from": [ "Standard.A", "Standard.X" ], "to": "Actions.UiNavSelect" },
{ "from": [ "Standard.B", "Standard.Y" ], "to": "Actions.UiNavBack" },
{
"from": [ "Standard.RT", "Standard.LT" ],
"to": "Actions.UiNavSelect",
"filters": [
{ "type": "deadZone", "min": 0.5 },
"constrainToInteger"
]
},
{ "from": [ "Standard.RTClick", "Standard.LTClick" ], "to": "Actions.UiNavSelect" },
{
"from": "Standard.LX", "to": "Actions.UiNavLateral",
"filters": [

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

@ -45,6 +45,7 @@ FocusScope {
onVisibleChanged: recalcSize();
onCountChanged: recalcSize();
focus: true
highlightMoveDuration: 0
highlight: Rectangle {
anchors {

View file

@ -4705,6 +4705,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue,
RayToOverlayIntersectionResultFromScriptValue);
scriptEngine->registerGlobalObject("OffscreenFlags", DependencyManager::get<OffscreenUi>()->getFlags());
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data());

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

@ -29,6 +29,9 @@ class OffscreenFlags : public QObject {
Q_OBJECT
Q_PROPERTY(bool navigationFocused READ isNavigationFocused WRITE setNavigationFocused NOTIFY navigationFocusedChanged)
// Allow scripts that are doing their own navigation support to disable navigation focus (i.e. handControllerPointer.js)
Q_PROPERTY(bool navigationFocusDisabled READ isNavigationFocusDisabled WRITE setNavigationFocusDisabled NOTIFY navigationFocusDisabledChanged)
public:
OffscreenFlags(QObject* parent = nullptr) : QObject(parent) {}
@ -40,11 +43,21 @@ public:
}
}
bool isNavigationFocusDisabled() const { return _navigationFocusDisabled; }
void setNavigationFocusDisabled(bool disabled) {
if (_navigationFocusDisabled != disabled) {
_navigationFocusDisabled = disabled;
emit navigationFocusDisabledChanged();
}
}
signals:
void navigationFocusedChanged();
void navigationFocusDisabledChanged();
private:
bool _navigationFocused { false };
bool _navigationFocusDisabled{ false };
};
QString fixupHifiUrl(const QString& urlString) {
@ -103,6 +116,10 @@ bool OffscreenUi::shouldSwallowShortcut(QEvent* event) {
OffscreenUi::OffscreenUi() {
}
QObject* OffscreenUi::getFlags() {
return offscreenFlags;
}
void OffscreenUi::create(QOpenGLContext* context) {
OffscreenQmlSurface::create(context);
auto rootContext = getRootContext();
@ -392,7 +409,7 @@ QVariant OffscreenUi::waitForInputDialogResult(QQuickItem* inputDialog) {
}
bool OffscreenUi::navigationFocused() {
return offscreenFlags->isNavigationFocused();
return !offscreenFlags->isNavigationFocusDisabled() && offscreenFlags->isNavigationFocused();
}
void OffscreenUi::setNavigationFocused(bool focused) {

View file

@ -55,7 +55,7 @@ public:
bool eventFilter(QObject* originalDestination, QEvent* event) override;
void addMenuInitializer(std::function<void(VrMenu*)> f);
QObject* getFlags();
QQuickItem* getDesktop();
QQuickItem* getToolWindow();

View file

@ -22,6 +22,7 @@
#include <UserActivityLogger.h>
#include <OffscreenUi.h>
#include <controllers/UserInputMapper.h>
#include <controllers/StandardControls.h>
@ -284,7 +285,6 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u
vr::VRControllerState_t controllerState = vr::VRControllerState_t();
if (_system->GetControllerState(deviceIndex, &controllerState)) {
// process each button
for (uint32_t i = 0; i < vr::k_EButton_Max; ++i) {
auto mask = vr::ButtonMaskFromId((vr::EVRButtonId)i);
@ -342,6 +342,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 +468,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 COLLIDE_WITH_AV_AFTER_RELEASE_DELAY = 0.25; // seconds
@ -393,6 +392,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;
@ -852,6 +852,10 @@ function MyController(hand) {
_this.rawTriggerValue = value;
};
this.triggerClick = function (value) {
_this.triggerClicked = value;
};
this.secondaryPress = function (value) {
_this.rawSecondaryValue = value;
};
@ -864,7 +868,7 @@ function MyController(hand) {
};
this.triggerSmoothedGrab = function () {
return this.triggerValue > TRIGGER_GRAB_VALUE;
return this.triggerClicked;
};
this.triggerSmoothedSqueezed = function () {
@ -2293,7 +2297,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,23 +49,23 @@ 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.triggerPress = function (value) {
that.rawTriggerValue = value;
};
that.triggerClicked = false;
that.triggerClick = function (value) { that.triggerClicked = value; };
that.triggerPress = function (value) { that.rawTriggerValue = value; };
that.updateSmoothedTrigger = function () { // e.g., call once/update for effect
var triggerValue = that.rawTriggerValue;
// smooth out trigger value
that.triggerValue = (that.triggerValue * that.TRIGGER_SMOOTH_RATIO) +
(triggerValue * (1.0 - that.TRIGGER_SMOOTH_RATIO));
OffscreenFlags.navigationFocusDisabled = that.triggerValue != 0.0;
};
// 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 +81,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 +365,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.
@ -493,7 +495,10 @@ function checkSettings() {
updateRecommendedArea();
}
checkSettings();
var settingsChecker = Script.setInterval(checkSettings, SETTINGS_CHANGE_RECHECK_INTERVAL);
Script.scriptEnding.connect(function () {
Script.clearInterval(settingsChecker);
OffscreenFlags.navigationFocusDisabled = false;
});

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