Merge pull request #12087 from ctrlaltdavid/21661

Laser auto-on with tablet and Web overlays
This commit is contained in:
Andrew Meadows 2018-01-05 14:08:01 -08:00 committed by GitHub
commit b28684f15e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 265 additions and 108 deletions

View file

@ -738,6 +738,7 @@ const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 100.0f;
const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f;
const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
const bool DEFAULT_PREFER_STYLUS_OVER_LASER = false;
const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
const QString DEFAULT_CURSOR_NAME = "DEFAULT";
@ -757,6 +758,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT),
_desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR),
_hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR),
_preferStylusOverLaserSetting("preferStylusOverLaser", DEFAULT_PREFER_STYLUS_OVER_LASER),
_preferAvatarFingerOverStylusSetting("preferAvatarFingerOverStylus", DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS),
_constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
_preferredCursor("preferredCursor", DEFAULT_CURSOR_NAME),
@ -2580,6 +2582,10 @@ void Application::setHmdTabletBecomesToolbarSetting(bool value) {
updateSystemTabletMode();
}
void Application::setPreferStylusOverLaser(bool value) {
_preferStylusOverLaserSetting.set(value);
}
void Application::setPreferAvatarFingerOverStylus(bool value) {
_preferAvatarFingerOverStylusSetting.set(value);
}

View file

@ -207,7 +207,11 @@ public:
void setDesktopTabletBecomesToolbarSetting(bool value);
bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); }
void setHmdTabletBecomesToolbarSetting(bool value);
bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); }
bool getPreferStylusOverLaser() { return _preferStylusOverLaserSetting.get(); }
void setPreferStylusOverLaser(bool value);
// FIXME: Remove setting completely or make available through JavaScript API?
//bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); }
bool getPreferAvatarFingerOverStylus() { return false; }
void setPreferAvatarFingerOverStylus(bool value);
float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); }
@ -551,6 +555,7 @@ private:
Setting::Handle<float> _desktopTabletScale;
Setting::Handle<bool> _desktopTabletBecomesToolbarSetting;
Setting::Handle<bool> _hmdTabletBecomesToolbarSetting;
Setting::Handle<bool> _preferStylusOverLaserSetting;
Setting::Handle<bool> _preferAvatarFingerOverStylusSetting;
Setting::Handle<bool> _constrainToolbarPosition;
Setting::Handle<QString> _preferredCursor;

View file

@ -218,14 +218,40 @@ Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pi
return PickedObject(rayPickResult->objectID, rayPickResult->type);
}
Pointer::Buttons LaserPointer::getPressedButtons() {
Pointer::Buttons LaserPointer::getPressedButtons(const PickResultPointer& pickResult) {
std::unordered_set<std::string> toReturn;
for (const PointerTrigger& trigger : _triggers) {
// TODO: right now, LaserPointers don't support axes, only on/off buttons
if (trigger.getEndpoint()->peek() >= 1.0f) {
toReturn.insert(trigger.getButton());
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
if (rayPickResult) {
for (const PointerTrigger& trigger : _triggers) {
std::string button = trigger.getButton();
TriggerState& state = _states[button];
// TODO: right now, LaserPointers don't support axes, only on/off buttons
if (trigger.getEndpoint()->peek() >= 1.0f) {
toReturn.insert(button);
if (_previousButtons.find(button) == _previousButtons.end()) {
// start triggering for buttons that were just pressed
state.triggeredObject = PickedObject(rayPickResult->objectID, rayPickResult->type);
state.intersection = rayPickResult->intersection;
state.triggerPos2D = findPos2D(state.triggeredObject, rayPickResult->intersection);
state.triggerStartTime = usecTimestampNow();
state.surfaceNormal = rayPickResult->surfaceNormal;
state.deadspotExpired = false;
state.wasTriggering = true;
state.triggering = true;
_latestState = state;
}
} else {
// stop triggering for buttons that aren't pressed
state.wasTriggering = state.triggering;
state.triggering = false;
_latestState = state;
}
}
_previousButtons = toReturn;
}
return toReturn;
}
@ -303,7 +329,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
return RenderState(startID, pathID, endID);
}
PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover) const {
PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) {
QUuid pickedID;
glm::vec3 intersection, surfaceNormal, direction, origin;
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
@ -316,20 +342,48 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P
pickedID = rayPickResult->objectID;
}
glm::vec2 pos2D;
if (pickedID != target.objectID) {
if (target.type == ENTITY) {
intersection = RayPick::intersectRayWithEntityXYPlane(target.objectID, origin, direction);
} else if (target.type == OVERLAY) {
intersection = RayPick::intersectRayWithOverlayXYPlane(target.objectID, origin, direction);
}
intersection = findIntersection(target, origin, direction);
}
if (target.type == ENTITY) {
pos2D = RayPick::projectOntoEntityXYPlane(target.objectID, intersection);
} else if (target.type == OVERLAY) {
pos2D = RayPick::projectOntoOverlayXYPlane(target.objectID, intersection);
} else if (target.type == HUD) {
pos2D = DependencyManager::get<PickManager>()->calculatePos2DFromHUD(intersection);
glm::vec2 pos2D = findPos2D(target, intersection);
// If we just started triggering and we haven't moved too much, don't update intersection and pos2D
TriggerState& state = hover ? _latestState : _states[button];
float sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale;
bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared;
if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) {
pos2D = state.triggerPos2D;
intersection = state.intersection;
surfaceNormal = state.surfaceNormal;
}
if (!withinDeadspot) {
state.deadspotExpired = true;
}
return PointerEvent(pos2D, intersection, surfaceNormal, direction);
}
glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) {
switch (pickedObject.type) {
case ENTITY:
return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction);
case OVERLAY:
return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction);
default:
return glm::vec3(NAN);
}
}
glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) {
switch (pickedObject.type) {
case ENTITY:
return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin);
case OVERLAY:
return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin);
case HUD:
return DependencyManager::get<PickManager>()->calculatePos2DFromHUD(origin);
default:
return glm::vec2(NAN);
}
}

View file

@ -80,10 +80,10 @@ public:
static RenderState buildRenderState(const QVariantMap& propMap);
protected:
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const override;
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Pointer::Buttons getPressedButtons() override;
Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override;
bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
@ -105,6 +105,23 @@ private:
void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState);
void disableRenderState(const RenderState& renderState);
struct TriggerState {
PickedObject triggeredObject;
glm::vec3 intersection { NAN };
glm::vec3 surfaceNormal { NAN };
glm::vec2 triggerPos2D { NAN };
quint64 triggerStartTime { 0 };
bool deadspotExpired { true };
bool triggering { false };
bool wasTriggering { false };
};
Pointer::Buttons _previousButtons;
std::unordered_map<std::string, TriggerState> _states;
TriggerState _latestState;
static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction);
static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin);
};
#endif // hifi_LaserPointer_h

View file

@ -92,4 +92,4 @@ glm::vec2 RayPick::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::
glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) {
auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint(), unNormalized);
}
}

View file

@ -28,10 +28,6 @@ static const float TABLET_MAX_TOUCH_DISTANCE = 0.005f;
static const float HOVER_HYSTERESIS = 0.01f;
static const float TOUCH_HYSTERESIS = 0.001f;
static const float STYLUS_MOVE_DELAY = 0.33f * USECS_PER_SECOND;
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f;
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED = TOUCH_PRESS_TO_MOVE_DEADSPOT * TOUCH_PRESS_TO_MOVE_DEADSPOT;
StylusPointer::StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled) :
Pointer(DependencyManager::get<PickScriptingInterface>()->createStylusPick(props), enabled, hover),
_stylusOverlay(stylusOverlay)
@ -112,37 +108,37 @@ bool StylusPointer::shouldHover(const PickResultPointer& pickResult) {
bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) {
auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);
bool wasTriggering = false;
if (_renderState == EVENTS_ON && stylusPickResult) {
auto sensorScaleFactor = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
float distance = stylusPickResult->distance;
// If we're triggering on an object, recalculate the distance instead of using the pickResult
glm::vec3 origin = vec3FromVariant(stylusPickResult->pickVariant["position"]);
glm::vec3 direction = -_state.surfaceNormal;
if (!_state.triggeredObject.objectID.isNull() && stylusPickResult->objectID != _state.triggeredObject.objectID) {
glm::vec3 direction = _state.triggering ? -_state.surfaceNormal : -stylusPickResult->surfaceNormal;
if ((_state.triggering || _state.wasTriggering) && stylusPickResult->objectID != _state.triggeredObject.objectID) {
distance = glm::dot(findIntersection(_state.triggeredObject, origin, direction) - origin, direction);
}
float hysteresis = _state.triggering ? TOUCH_HYSTERESIS * sensorScaleFactor : 0.0f;
if (isWithinBounds(distance, TABLET_MIN_TOUCH_DISTANCE * sensorScaleFactor,
TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor, hysteresis)) {
if (_state.triggeredObject.objectID.isNull()) {
TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor, hysteresis)) {
_state.wasTriggering = _state.triggering;
if (!_state.triggering) {
_state.triggeredObject = PickedObject(stylusPickResult->objectID, stylusPickResult->type);
_state.intersection = findIntersection(_state.triggeredObject, origin, direction);
_state.intersection = stylusPickResult->intersection;
_state.triggerPos2D = findPos2D(_state.triggeredObject, origin);
_state.triggerStartTime = usecTimestampNow();
_state.surfaceNormal = stylusPickResult->surfaceNormal;
_state.deadspotExpired = false;
_state.triggering = true;
}
return true;
}
wasTriggering = _state.triggering;
}
_state.triggeredObject = PickedObject();
_state.intersection = glm::vec3(NAN);
_state.triggerPos2D = glm::vec2(NAN);
_state.triggerStartTime = 0;
_state.surfaceNormal = glm::vec3(NAN);
_state.wasTriggering = wasTriggering;
_state.triggering = false;
return false;
}
@ -155,13 +151,13 @@ Pointer::PickedObject StylusPointer::getHoveredObject(const PickResultPointer& p
return PickedObject(stylusPickResult->objectID, stylusPickResult->type);
}
Pointer::Buttons StylusPointer::getPressedButtons() {
Pointer::Buttons StylusPointer::getPressedButtons(const PickResultPointer& pickResult) {
// TODO: custom buttons for styluses
Pointer::Buttons toReturn({ "Primary", "Focus" });
return toReturn;
}
PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover) const {
PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) {
QUuid pickedID;
glm::vec2 pos2D;
glm::vec3 intersection, surfaceNormal, direction, origin;
@ -177,18 +173,22 @@ PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const
}
// If we just started triggering and we haven't moved too much, don't update intersection and pos2D
if (!_state.triggeredObject.objectID.isNull() && usecTimestampNow() - _state.triggerStartTime < STYLUS_MOVE_DELAY &&
glm::distance2(pos2D, _state.triggerPos2D) < TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED) {
float sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale;
bool withinDeadspot = usecTimestampNow() - _state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, _state.triggerPos2D) < deadspotSquared;
if ((_state.triggering || _state.wasTriggering) && !_state.deadspotExpired && withinDeadspot) {
pos2D = _state.triggerPos2D;
intersection = _state.intersection;
} else if (pickedID != target.objectID) {
intersection = findIntersection(target, origin, direction);
}
if (!withinDeadspot) {
_state.deadspotExpired = true;
}
return PointerEvent(pos2D, intersection, surfaceNormal, direction);
}
bool StylusPointer::isWithinBounds(float distance, float min, float max, float hysteresis) {
return (distance == glm::clamp(distance, min - hysteresis, max + hysteresis));
}

View file

@ -37,11 +37,11 @@ public:
protected:
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Buttons getPressedButtons() override;
Buttons getPressedButtons(const PickResultPointer& pickResult) override;
bool shouldHover(const PickResultPointer& pickResult) override;
bool shouldTrigger(const PickResultPointer& pickResult) override;
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const override;
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
private:
void show(const StylusTip& tip);
@ -53,7 +53,9 @@ private:
glm::vec2 triggerPos2D { NAN };
glm::vec3 surfaceNormal { NAN };
quint64 triggerStartTime { 0 };
bool deadspotExpired { true };
bool triggering { false };
bool wasTriggering { false };
bool hovering { false };
};

View file

@ -90,11 +90,19 @@ void setupPreferences() {
preference->setMax(500);
preferences->addPreference(preference);
}
{
auto getter = []()->bool { return qApp->getPreferStylusOverLaser(); };
auto setter = [](bool value) { qApp->setPreferStylusOverLaser(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Stylus Over Laser", getter, setter));
}
// FIXME: Remove setting completely or make available through JavaScript API?
/*
{
auto getter = []()->bool { return qApp->getPreferAvatarFingerOverStylus(); };
auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter));
}
*/
{
static const QString RETICLE_ICON_NAME = { Cursor::Manager::getIconName(Cursor::Icon::RETICLE) };
auto getter = []()->bool { return qApp->getPreferredCursor() == RETICLE_ICON_NAME; };

View file

@ -11,6 +11,12 @@
#include "PickManager.h"
#include "PointerManager.h"
#include "NumericalConstants.h"
const float Pointer::POINTER_MOVE_DELAY = 0.33f * USECS_PER_SECOND;
const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f;
const float Pointer::TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED = TOUCH_PRESS_TO_MOVE_DEADSPOT * TOUCH_PRESS_TO_MOVE_DEADSPOT;
Pointer::~Pointer() {
DependencyManager::get<PickManager>()->removePick(_pickUID);
}
@ -77,7 +83,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
Buttons newButtons;
Buttons sameButtons;
if (_enabled && shouldTrigger(pickResult)) {
buttons = getPressedButtons();
buttons = getPressedButtons(pickResult);
for (const std::string& button : buttons) {
if (_prevButtons.find(button) == _prevButtons.end()) {
newButtons.insert(button);
@ -175,17 +181,6 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
}
}
// send hoverEnd events if we disable the pointer or disable hovering
if (_hover && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) {
if (_prevHoveredObject.type == ENTITY) {
emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent);
} else if (_prevHoveredObject.type == OVERLAY) {
emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent);
} else if (_prevHoveredObject.type == HUD) {
emit pointerManager->hoverEndHUD(hoveredEvent);
}
}
// Trigger begin
const std::string SHOULD_FOCUS_BUTTON = "Focus";
for (const std::string& button : newButtons) {
@ -204,7 +199,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
// Trigger continue
for (const std::string& button : sameButtons) {
PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, false);
PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, button, false);
triggeredEvent.setID(pointerID);
triggeredEvent.setType(PointerEvent::Move);
triggeredEvent.setButton(chooseButton(button));
@ -219,7 +214,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
// Trigger end
for (const std::string& button : _prevButtons) {
PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, false);
PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, button, false);
triggeredEvent.setID(pointerID);
triggeredEvent.setType(PointerEvent::Release);
triggeredEvent.setButton(chooseButton(button));
@ -233,6 +228,17 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
_triggeredObjects.erase(button);
}
// if we disable the pointer or disable hovering, send hoverEnd events after triggerEnd
if (_hover && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) {
if (_prevHoveredObject.type == ENTITY) {
emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent);
} else if (_prevHoveredObject.type == OVERLAY) {
emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent);
} else if (_prevHoveredObject.type == HUD) {
emit pointerManager->hoverEndHUD(hoveredEvent);
}
}
_prevHoveredObject = hoveredObject;
_prevButtons = buttons;
_prevEnabled = _enabled;

View file

@ -82,14 +82,17 @@ protected:
bool _enabled;
bool _hover;
virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const = 0;
virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) = 0;
virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) = 0;
virtual Buttons getPressedButtons() = 0;
virtual Buttons getPressedButtons(const PickResultPointer& pickResult) = 0;
virtual bool shouldHover(const PickResultPointer& pickResult) { return true; }
virtual bool shouldTrigger(const PickResultPointer& pickResult) { return true; }
static const float POINTER_MOVE_DELAY;
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED;
private:
PickedObject _prevHoveredObject;
Buttons _prevButtons;

View file

@ -145,11 +145,11 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
return deltaTime;
};
this.setIgnoreTablet = function() {
this.setIgnorePointerItems = function() {
if (HMD.tabletID !== this.tabletID) {
this.tabletID = HMD.tabletID;
Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist.concat([HMD.tabletID]));
Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist.concat([HMD.tabletID]));
Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist);
Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist);
}
};
@ -168,7 +168,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
}
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
var deltaTime = _this.updateTimings();
_this.setIgnoreTablet();
_this.setIgnorePointerItems();
if (controllerDispatcherPluginsNeedSort) {
_this.orderedPluginNames = [];
@ -182,16 +182,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
controllerDispatcherPlugins[b].parameters.priority;
});
var output = "controllerDispatcher -- new plugin order: ";
for (var k = 0; k < _this.orderedPluginNames.length; k++) {
var dbgPluginName = _this.orderedPluginNames[k];
var priority = controllerDispatcherPlugins[dbgPluginName].parameters.priority;
output += dbgPluginName + ":" + priority;
if (k + 1 < _this.orderedPluginNames.length) {
output += ", ";
}
}
controllerDispatcherPluginsNeedSort = false;
}
@ -388,8 +378,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
};
this.setBlacklist = function() {
RayPick.setIgnoreItems(_this.leftControllerRayPick, this.blacklist.concat(HMD.tabletID));
RayPick.setIgnoreItems(_this.rightControllerRayPick, this.blacklist.concat(HMD.tabletID));
RayPick.setIgnoreItems(_this.leftControllerRayPick, this.blacklist);
RayPick.setIgnoreItems(_this.rightControllerRayPick, this.blacklist);
};
var MAPPING_NAME = "com.highfidelity.controllerDispatcher";

View file

@ -14,7 +14,7 @@
PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD,
DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic,
getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI
Picks, makeLaserLockInfo Xform
Picks, makeLaserLockInfo Xform, makeLaserParams
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -119,7 +119,7 @@ Script.include("/~/system/libraries/Xform.js");
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[],
100,
this.hand);
makeLaserParams(this.hand, false));
this.handToController = function() {

View file

@ -9,7 +9,7 @@
/* global Script, Controller, LaserPointers, RayPick, RIGHT_HAND, LEFT_HAND, MyAvatar, getGrabPointSphereOffset,
makeRunningValues, Entities, enableDispatcherModule, disableDispatcherModule, makeDispatcherModuleParameters,
PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD,
DEFAULT_SEARCH_SPHERE_DISTANCE, getGrabbableData
DEFAULT_SEARCH_SPHERE_DISTANCE, getGrabbableData, makeLaserParams
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -34,7 +34,7 @@ Script.include("/~/system/libraries/controllers.js");
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[],
100,
this.hand);
makeLaserParams(this.hand, false));
this.getTargetProps = function (controllerData) {
// nearbyEntityProperties is already sorted by length from controller

View file

@ -16,7 +16,8 @@
makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION,
PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD,
DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic,
getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI
getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI,
makeLaserParams
*/
(function() {
@ -36,7 +37,7 @@
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[],
100,
(this.hand + HUD_LASER_OFFSET));
makeLaserParams((this.hand + HUD_LASER_OFFSET), false));
this.getOtherHandController = function() {
return (this.hand === RIGHT_HAND) ? Controller.Standard.LeftHand : Controller.Standard.RightHand;

View file

@ -10,7 +10,7 @@
/* global Script, Controller, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, makeRunningValues,
Messages, makeDispatcherModuleParameters, HMD, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE,
getEnabledModuleByName, PICK_MAX_DISTANCE, isInEditMode, LaserPointers, RayPick, Picks
getEnabledModuleByName, PICK_MAX_DISTANCE, isInEditMode, LaserPointers, RayPick, Picks, makeLaserParams
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -28,7 +28,7 @@ Script.include("/~/system/libraries/utils.js");
this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"],
[],
100,
this.hand);
makeLaserParams(this.hand, false));
this.nearTablet = function(overlays) {
for (var i = 0; i < overlays.length; i++) {
@ -44,10 +44,7 @@ Script.include("/~/system/libraries/utils.js");
};
this.pointingAtTablet = function(objectID) {
if (objectID === HMD.tabletScreenID || objectID === HMD.homeButtonID) {
return true;
}
return false;
return objectID === HMD.tabletScreenID || objectID === HMD.homeButtonID;
};
this.sendPickData = function(controllerData) {
@ -99,8 +96,8 @@ Script.include("/~/system/libraries/utils.js");
var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput");
if (overlayLaser) {
var overlayLaserReady = overlayLaser.isReady(controllerData);
if (overlayLaserReady.active && this.pointingAtTablet(overlayLaser.target)) {
var target = controllerData.rayPicks[this.hand].objectID;
if (overlayLaserReady.active && this.pointingAtTablet(target)) {
return this.exitModule();
}
}

View file

@ -9,7 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global Script, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule,
makeDispatcherModuleParameters, makeRunningValues, getEnabledModuleByName
makeDispatcherModuleParameters, makeRunningValues, getEnabledModuleByName, makeLaserParams
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -19,15 +19,21 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
function InVREditMode(hand) {
this.hand = hand;
this.disableModules = false;
var NO_HAND_LASER = -1; // Invalid hand parameter so that default laser is not displayed.
this.parameters = makeDispatcherModuleParameters(
200, // Not too high otherwise the tablet laser doesn't work.
this.hand === RIGHT_HAND
? ["rightHand", "rightHandEquip", "rightHandTrigger"]
: ["leftHand", "leftHandEquip", "leftHandTrigger"],
[],
100
100,
makeLaserParams(NO_HAND_LASER, false)
);
this.pointingAtTablet = function (objectID) {
return objectID === HMD.tabletScreenID || objectID === HMD.homeButtonID;
};
this.isReady = function (controllerData) {
if (this.disableModules) {
return makeRunningValues(true, [], []);
@ -42,7 +48,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
}
// Tablet stylus.
// Includes the tablet laser.
var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightTabletStylusInput"
: "LeftTabletStylusInput");
@ -53,6 +58,18 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
}
}
// Tablet surface.
var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightWebSurfaceLaserInput"
: "LeftWebSurfaceLaserInput");
if (overlayLaser) {
var overlayLaserReady = overlayLaser.isReady(controllerData);
var target = controllerData.rayPicks[this.hand].objectID;
if (overlayLaserReady.active && this.pointingAtTablet(target)) {
return makeRunningValues(false, [], []);
}
}
// Tablet grabbing.
var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightNearParentingGrabOverlay"

View file

@ -142,7 +142,10 @@ Script.include("/~/system/libraries/controllers.js");
};
this.isReady = function (controllerData) {
if (this.processStylus(controllerData)) {
var PREFER_STYLUS_OVER_LASER = "preferStylusOverLaser";
var isUsingStylus = Settings.getValue(PREFER_STYLUS_OVER_LASER, false);
if (isUsingStylus && this.processStylus(controllerData)) {
Pointers.enablePointer(this.pointer);
return makeRunningValues(true, [], []);
} else {

View file

@ -9,7 +9,7 @@
makeRunningValues, Messages, Quat, Vec3, makeDispatcherModuleParameters, Overlays, ZERO_VEC, HMD,
INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE,
TRIGGER_OFF_VALUE, getEnabledModuleByName, PICK_MAX_DISTANCE, LaserPointers, RayPick, ContextOverlay, Picks
TRIGGER_OFF_VALUE, getEnabledModuleByName, PICK_MAX_DISTANCE, LaserPointers, RayPick, ContextOverlay, Picks, makeLaserParams
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -18,6 +18,7 @@ Script.include("/~/system/libraries/controllers.js");
(function() {
function WebSurfaceLaserInput(hand) {
this.hand = hand;
this.otherHand = this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND;
this.running = false;
this.parameters = makeDispatcherModuleParameters(
@ -25,7 +26,7 @@ Script.include("/~/system/libraries/controllers.js");
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[],
100,
this.hand);
makeLaserParams(hand, true));
this.grabModuleWantsNearbyOverlay = function(controllerData) {
if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) {
@ -65,7 +66,8 @@ Script.include("/~/system/libraries/controllers.js");
};
this.deleteContextOverlay = function() {
var farGrabModule = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity");
var farGrabModule = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity");
if (farGrabModule) {
var entityWithContextOverlay = farGrabModule.entityWithContextOverlay;
@ -76,25 +78,50 @@ Script.include("/~/system/libraries/controllers.js");
}
};
this.isReady = function (controllerData) {
this.updateAllwaysOn = function() {
var PREFER_STYLUS_OVER_LASER = "preferStylusOverLaser";
this.parameters.handLaser.allwaysOn = !Settings.getValue(PREFER_STYLUS_OVER_LASER, false);
};
this.getDominantHand = function() {
return MyAvatar.getDominantHand() === "right" ? 1 : 0;
};
this.dominantHandOverride = false;
this.isReady = function(controllerData) {
var otherModuleRunning = this.getOtherModule().running;
if ((this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)) &&
!otherModuleRunning) {
if (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE) {
otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand.
var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE
&& controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE;
if ((!otherModuleRunning || isTriggerPressed)
&& (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData))) {
this.updateAllwaysOn();
if (isTriggerPressed) {
this.dominantHandOverride = true; // Override dominant hand.
this.getOtherModule().dominantHandOverride = false;
}
if (this.parameters.handLaser.allwaysOn || isTriggerPressed) {
return makeRunningValues(true, [], []);
}
}
return makeRunningValues(false, [], []);
};
this.run = function (controllerData, deltaTime) {
this.run = function(controllerData, deltaTime) {
var otherModuleRunning = this.getOtherModule().running;
otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand.
otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand.
var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData);
if (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && !grabModuleNeedsToRun) {
if (!otherModuleRunning && !grabModuleNeedsToRun && (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE
|| this.parameters.handLaser.allwaysOn
&& (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)))) {
this.running = true;
return makeRunningValues(true, [], []);
}
this.deleteContextOverlay();
this.running = false;
this.dominantHandOverride = false;
return makeRunningValues(false, [], []);
};
}

View file

@ -118,7 +118,8 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
Overlays.deleteOverlay(this.webOverlayID);
}
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor;
var RAYPICK_OFFSET = 0.0001; // Sufficient for raypick to reliably intersect tablet screen before tablet model.
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor + RAYPICK_OFFSET;
var WEB_ENTITY_Y_OFFSET = 0.004;
var screenWidth = 0.82 * tabletWidth;
var screenHeight = 0.81 * tabletHeight;

View file

@ -45,6 +45,7 @@
BUMPER_ON_VALUE:true,
getEntityParents:true,
findHandChildEntities:true,
makeLaserParams:true,
TEAR_AWAY_DISTANCE:true,
TEAR_AWAY_COUNT:true,
TEAR_AWAY_CHECK_TIME:true,
@ -134,6 +135,17 @@ makeLaserLockInfo = function(targetID, isOverlay, hand, offset) {
};
};
makeLaserParams = function(hand, allwaysOn) {
if (allwaysOn === undefined) {
allwaysOn = false;
}
return {
hand: hand,
allwaysOn: allwaysOn
};
};
makeRunningValues = function (active, targets, requiredDataForRun, laserLockInfo) {
return {
active: active,

View file

@ -95,6 +95,7 @@ Pointer = function(hudLayer, pickType, pointerData) {
this.pointerID = null;
this.visible = false;
this.locked = false;
this.allwaysOn = false;
this.hand = pointerData.hand;
delete pointerData.hand;
@ -150,7 +151,7 @@ Pointer = function(hudLayer, pickType, pointerData) {
mode = "hold";
} else if (triggerClicks[this.hand]) {
mode = "full";
} else if (triggerValues[this.hand] > TRIGGER_ON_VALUE) {
} else if (triggerValues[this.hand] > TRIGGER_ON_VALUE || this.allwaysOn) {
mode = "half";
}
}
@ -172,19 +173,23 @@ PointerManager = function() {
return pointer.pointerID;
};
this.makePointerVisible = function(index) {
this.makePointerVisible = function(laserParams) {
var index = laserParams.hand;
if (index < this.pointers.length && index >= 0) {
this.pointers[index].makeVisible();
this.pointers[index].allwaysOn = laserParams.allwaysOn;
}
};
this.makePointerInvisible = function(index) {
this.makePointerInvisible = function(laserParams) {
var index = laserParams.hand;
if (index < this.pointers.length && index >= 0) {
this.pointers[index].makeInvisible();
}
};
this.lockPointerEnd = function(index, lockData) {
this.lockPointerEnd = function(laserParams, lockData) {
var index = laserParams.hand;
if (index < this.pointers.length && index >= 0) {
this.pointers[index].lockEnd(lockData);
}

View file

@ -400,7 +400,8 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride)
});
// update webOverlay
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride;
var RAYPICK_OFFSET = 0.0001; // Sufficient for raypick to reliably intersect tablet screen before tablet model.
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride + RAYPICK_OFFSET;
var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleFactor * sensorScaleOffsetOverride;
var screenWidth = 0.82 * tabletWidth;
var screenHeight = 0.81 * tabletHeight;
@ -417,11 +418,13 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride)
var homeButtonDim = 4.0 * tabletScaleFactor / 3.0;
Overlays.editOverlay(HMD.homeButtonID, {
localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET },
localRotation: Quat.angleAxis(180, Vec3.UNIT_Y),
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }
});
Overlays.editOverlay(HMD.homeButtonHighlightID, {
localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET },
localRotation: Quat.angleAxis(180, Vec3.UNIT_Y),
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }
});
};