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 float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f;
const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true; const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false; 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 bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
const QString DEFAULT_CURSOR_NAME = "DEFAULT"; 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), _desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT),
_desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR), _desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR),
_hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_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), _preferAvatarFingerOverStylusSetting("preferAvatarFingerOverStylus", DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS),
_constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true), _constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
_preferredCursor("preferredCursor", DEFAULT_CURSOR_NAME), _preferredCursor("preferredCursor", DEFAULT_CURSOR_NAME),
@ -2580,6 +2582,10 @@ void Application::setHmdTabletBecomesToolbarSetting(bool value) {
updateSystemTabletMode(); updateSystemTabletMode();
} }
void Application::setPreferStylusOverLaser(bool value) {
_preferStylusOverLaserSetting.set(value);
}
void Application::setPreferAvatarFingerOverStylus(bool value) { void Application::setPreferAvatarFingerOverStylus(bool value) {
_preferAvatarFingerOverStylusSetting.set(value); _preferAvatarFingerOverStylusSetting.set(value);
} }

View file

@ -207,7 +207,11 @@ public:
void setDesktopTabletBecomesToolbarSetting(bool value); void setDesktopTabletBecomesToolbarSetting(bool value);
bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); } bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); }
void setHmdTabletBecomesToolbarSetting(bool value); 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); void setPreferAvatarFingerOverStylus(bool value);
float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); } float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); }
@ -551,6 +555,7 @@ private:
Setting::Handle<float> _desktopTabletScale; Setting::Handle<float> _desktopTabletScale;
Setting::Handle<bool> _desktopTabletBecomesToolbarSetting; Setting::Handle<bool> _desktopTabletBecomesToolbarSetting;
Setting::Handle<bool> _hmdTabletBecomesToolbarSetting; Setting::Handle<bool> _hmdTabletBecomesToolbarSetting;
Setting::Handle<bool> _preferStylusOverLaserSetting;
Setting::Handle<bool> _preferAvatarFingerOverStylusSetting; Setting::Handle<bool> _preferAvatarFingerOverStylusSetting;
Setting::Handle<bool> _constrainToolbarPosition; Setting::Handle<bool> _constrainToolbarPosition;
Setting::Handle<QString> _preferredCursor; Setting::Handle<QString> _preferredCursor;

View file

@ -218,14 +218,40 @@ Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pi
return PickedObject(rayPickResult->objectID, rayPickResult->type); return PickedObject(rayPickResult->objectID, rayPickResult->type);
} }
Pointer::Buttons LaserPointer::getPressedButtons() { Pointer::Buttons LaserPointer::getPressedButtons(const PickResultPointer& pickResult) {
std::unordered_set<std::string> toReturn; std::unordered_set<std::string> toReturn;
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
if (rayPickResult) {
for (const PointerTrigger& trigger : _triggers) { 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 // TODO: right now, LaserPointers don't support axes, only on/off buttons
if (trigger.getEndpoint()->peek() >= 1.0f) { if (trigger.getEndpoint()->peek() >= 1.0f) {
toReturn.insert(trigger.getButton()); 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; return toReturn;
} }
@ -303,7 +329,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
return RenderState(startID, pathID, endID); 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; QUuid pickedID;
glm::vec3 intersection, surfaceNormal, direction, origin; glm::vec3 intersection, surfaceNormal, direction, origin;
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult); auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
@ -316,20 +342,48 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P
pickedID = rayPickResult->objectID; pickedID = rayPickResult->objectID;
} }
glm::vec2 pos2D;
if (pickedID != target.objectID) { if (pickedID != target.objectID) {
if (target.type == ENTITY) { intersection = findIntersection(target, origin, direction);
intersection = RayPick::intersectRayWithEntityXYPlane(target.objectID, origin, direction);
} else if (target.type == OVERLAY) {
intersection = RayPick::intersectRayWithOverlayXYPlane(target.objectID, origin, direction);
} }
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 (target.type == ENTITY) { if (!withinDeadspot) {
pos2D = RayPick::projectOntoEntityXYPlane(target.objectID, intersection); state.deadspotExpired = true;
} else if (target.type == OVERLAY) {
pos2D = RayPick::projectOntoOverlayXYPlane(target.objectID, intersection);
} else if (target.type == HUD) {
pos2D = DependencyManager::get<PickManager>()->calculatePos2DFromHUD(intersection);
} }
return PointerEvent(pos2D, intersection, surfaceNormal, direction); 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); static RenderState buildRenderState(const QVariantMap& propMap);
protected: 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; 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 shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
bool shouldTrigger(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 updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState);
void disableRenderState(const RenderState& renderState); 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 #endif // hifi_LaserPointer_h

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

View file

@ -37,11 +37,11 @@ public:
protected: protected:
PickedObject getHoveredObject(const PickResultPointer& pickResult) override; PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Buttons getPressedButtons() override; Buttons getPressedButtons(const PickResultPointer& pickResult) override;
bool shouldHover(const PickResultPointer& pickResult) override; bool shouldHover(const PickResultPointer& pickResult) override;
bool shouldTrigger(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: private:
void show(const StylusTip& tip); void show(const StylusTip& tip);
@ -53,7 +53,9 @@ private:
glm::vec2 triggerPos2D { NAN }; glm::vec2 triggerPos2D { NAN };
glm::vec3 surfaceNormal { NAN }; glm::vec3 surfaceNormal { NAN };
quint64 triggerStartTime { 0 }; quint64 triggerStartTime { 0 };
bool deadspotExpired { true };
bool triggering { false }; bool triggering { false };
bool wasTriggering { false };
bool hovering { false }; bool hovering { false };
}; };

View file

@ -90,11 +90,19 @@ void setupPreferences() {
preference->setMax(500); preference->setMax(500);
preferences->addPreference(preference); 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 getter = []()->bool { return qApp->getPreferAvatarFingerOverStylus(); };
auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); }; auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter)); 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) }; static const QString RETICLE_ICON_NAME = { Cursor::Manager::getIconName(Cursor::Icon::RETICLE) };
auto getter = []()->bool { return qApp->getPreferredCursor() == RETICLE_ICON_NAME; }; auto getter = []()->bool { return qApp->getPreferredCursor() == RETICLE_ICON_NAME; };

View file

@ -11,6 +11,12 @@
#include "PickManager.h" #include "PickManager.h"
#include "PointerManager.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() { Pointer::~Pointer() {
DependencyManager::get<PickManager>()->removePick(_pickUID); DependencyManager::get<PickManager>()->removePick(_pickUID);
} }
@ -77,7 +83,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
Buttons newButtons; Buttons newButtons;
Buttons sameButtons; Buttons sameButtons;
if (_enabled && shouldTrigger(pickResult)) { if (_enabled && shouldTrigger(pickResult)) {
buttons = getPressedButtons(); buttons = getPressedButtons(pickResult);
for (const std::string& button : buttons) { for (const std::string& button : buttons) {
if (_prevButtons.find(button) == _prevButtons.end()) { if (_prevButtons.find(button) == _prevButtons.end()) {
newButtons.insert(button); 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 // Trigger begin
const std::string SHOULD_FOCUS_BUTTON = "Focus"; const std::string SHOULD_FOCUS_BUTTON = "Focus";
for (const std::string& button : newButtons) { for (const std::string& button : newButtons) {
@ -204,7 +199,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
// Trigger continue // Trigger continue
for (const std::string& button : sameButtons) { 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.setID(pointerID);
triggeredEvent.setType(PointerEvent::Move); triggeredEvent.setType(PointerEvent::Move);
triggeredEvent.setButton(chooseButton(button)); triggeredEvent.setButton(chooseButton(button));
@ -219,7 +214,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
// Trigger end // Trigger end
for (const std::string& button : _prevButtons) { 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.setID(pointerID);
triggeredEvent.setType(PointerEvent::Release); triggeredEvent.setType(PointerEvent::Release);
triggeredEvent.setButton(chooseButton(button)); triggeredEvent.setButton(chooseButton(button));
@ -233,6 +228,17 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
_triggeredObjects.erase(button); _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; _prevHoveredObject = hoveredObject;
_prevButtons = buttons; _prevButtons = buttons;
_prevEnabled = _enabled; _prevEnabled = _enabled;

View file

@ -82,14 +82,17 @@ protected:
bool _enabled; bool _enabled;
bool _hover; 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 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 shouldHover(const PickResultPointer& pickResult) { return true; }
virtual bool shouldTrigger(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: private:
PickedObject _prevHoveredObject; PickedObject _prevHoveredObject;
Buttons _prevButtons; Buttons _prevButtons;

View file

@ -145,11 +145,11 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
return deltaTime; return deltaTime;
}; };
this.setIgnoreTablet = function() { this.setIgnorePointerItems = function() {
if (HMD.tabletID !== this.tabletID) { if (HMD.tabletID !== this.tabletID) {
this.tabletID = HMD.tabletID; this.tabletID = HMD.tabletID;
Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist.concat([HMD.tabletID])); Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist);
Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist.concat([HMD.tabletID])); Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist);
} }
}; };
@ -168,7 +168,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
} }
var sensorScaleFactor = MyAvatar.sensorToWorldScale; var sensorScaleFactor = MyAvatar.sensorToWorldScale;
var deltaTime = _this.updateTimings(); var deltaTime = _this.updateTimings();
_this.setIgnoreTablet(); _this.setIgnorePointerItems();
if (controllerDispatcherPluginsNeedSort) { if (controllerDispatcherPluginsNeedSort) {
_this.orderedPluginNames = []; _this.orderedPluginNames = [];
@ -182,16 +182,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
controllerDispatcherPlugins[b].parameters.priority; 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; controllerDispatcherPluginsNeedSort = false;
} }
@ -388,8 +378,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
}; };
this.setBlacklist = function() { this.setBlacklist = function() {
RayPick.setIgnoreItems(_this.leftControllerRayPick, this.blacklist.concat(HMD.tabletID)); RayPick.setIgnoreItems(_this.leftControllerRayPick, this.blacklist);
RayPick.setIgnoreItems(_this.rightControllerRayPick, this.blacklist.concat(HMD.tabletID)); RayPick.setIgnoreItems(_this.rightControllerRayPick, this.blacklist);
}; };
var MAPPING_NAME = "com.highfidelity.controllerDispatcher"; 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, 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, 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
Picks, makeLaserLockInfo Xform Picks, makeLaserLockInfo Xform, makeLaserParams
*/ */
Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -119,7 +119,7 @@ Script.include("/~/system/libraries/Xform.js");
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[], [],
100, 100,
this.hand); makeLaserParams(this.hand, false));
this.handToController = function() { this.handToController = function() {

View file

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

View file

@ -16,7 +16,8 @@
makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, 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, 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, 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() { (function() {
@ -36,7 +37,7 @@
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[], [],
100, 100,
(this.hand + HUD_LASER_OFFSET)); makeLaserParams((this.hand + HUD_LASER_OFFSET), false));
this.getOtherHandController = function() { this.getOtherHandController = function() {
return (this.hand === RIGHT_HAND) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; 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, /* global Script, Controller, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, makeRunningValues,
Messages, makeDispatcherModuleParameters, HMD, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, 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, 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"); 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"], this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"],
[], [],
100, 100,
this.hand); makeLaserParams(this.hand, false));
this.nearTablet = function(overlays) { this.nearTablet = function(overlays) {
for (var i = 0; i < overlays.length; i++) { for (var i = 0; i < overlays.length; i++) {
@ -44,10 +44,7 @@ Script.include("/~/system/libraries/utils.js");
}; };
this.pointingAtTablet = function(objectID) { this.pointingAtTablet = function(objectID) {
if (objectID === HMD.tabletScreenID || objectID === HMD.homeButtonID) { return objectID === HMD.tabletScreenID || objectID === HMD.homeButtonID;
return true;
}
return false;
}; };
this.sendPickData = function(controllerData) { this.sendPickData = function(controllerData) {
@ -99,8 +96,8 @@ Script.include("/~/system/libraries/utils.js");
var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput"); var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput");
if (overlayLaser) { if (overlayLaser) {
var overlayLaserReady = overlayLaser.isReady(controllerData); var overlayLaserReady = overlayLaser.isReady(controllerData);
var target = controllerData.rayPicks[this.hand].objectID;
if (overlayLaserReady.active && this.pointingAtTablet(overlayLaser.target)) { if (overlayLaserReady.active && this.pointingAtTablet(target)) {
return this.exitModule(); return this.exitModule();
} }
} }

View file

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

View file

@ -142,7 +142,10 @@ Script.include("/~/system/libraries/controllers.js");
}; };
this.isReady = function (controllerData) { 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); Pointers.enablePointer(this.pointer);
return makeRunningValues(true, [], []); return makeRunningValues(true, [], []);
} else { } else {

View file

@ -9,7 +9,7 @@
makeRunningValues, Messages, Quat, Vec3, makeDispatcherModuleParameters, Overlays, ZERO_VEC, HMD, makeRunningValues, Messages, Quat, Vec3, makeDispatcherModuleParameters, Overlays, ZERO_VEC, HMD,
INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, 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, 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"); Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -18,6 +18,7 @@ Script.include("/~/system/libraries/controllers.js");
(function() { (function() {
function WebSurfaceLaserInput(hand) { function WebSurfaceLaserInput(hand) {
this.hand = hand; this.hand = hand;
this.otherHand = this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND;
this.running = false; this.running = false;
this.parameters = makeDispatcherModuleParameters( this.parameters = makeDispatcherModuleParameters(
@ -25,7 +26,7 @@ Script.include("/~/system/libraries/controllers.js");
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[], [],
100, 100,
this.hand); makeLaserParams(hand, true));
this.grabModuleWantsNearbyOverlay = function(controllerData) { this.grabModuleWantsNearbyOverlay = function(controllerData) {
if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) {
@ -65,7 +66,8 @@ Script.include("/~/system/libraries/controllers.js");
}; };
this.deleteContextOverlay = function() { this.deleteContextOverlay = function() {
var farGrabModule = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity"); var farGrabModule = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity");
if (farGrabModule) { if (farGrabModule) {
var entityWithContextOverlay = farGrabModule.entityWithContextOverlay; 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; var otherModuleRunning = this.getOtherModule().running;
if ((this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)) && otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand.
!otherModuleRunning) { var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE
if (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(true, [], []);
} }
} }
return makeRunningValues(false, [], []); 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); 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; this.running = true;
return makeRunningValues(true, [], []); return makeRunningValues(true, [], []);
} }
this.deleteContextOverlay(); this.deleteContextOverlay();
this.running = false; this.running = false;
this.dominantHandOverride = false;
return makeRunningValues(false, [], []); return makeRunningValues(false, [], []);
}; };
} }

View file

@ -118,7 +118,8 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
Overlays.deleteOverlay(this.webOverlayID); 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 WEB_ENTITY_Y_OFFSET = 0.004;
var screenWidth = 0.82 * tabletWidth; var screenWidth = 0.82 * tabletWidth;
var screenHeight = 0.81 * tabletHeight; var screenHeight = 0.81 * tabletHeight;

View file

@ -45,6 +45,7 @@
BUMPER_ON_VALUE:true, BUMPER_ON_VALUE:true,
getEntityParents:true, getEntityParents:true,
findHandChildEntities:true, findHandChildEntities:true,
makeLaserParams:true,
TEAR_AWAY_DISTANCE:true, TEAR_AWAY_DISTANCE:true,
TEAR_AWAY_COUNT:true, TEAR_AWAY_COUNT:true,
TEAR_AWAY_CHECK_TIME: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) { makeRunningValues = function (active, targets, requiredDataForRun, laserLockInfo) {
return { return {
active: active, active: active,

View file

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

View file

@ -400,7 +400,8 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride)
}); });
// update webOverlay // 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 WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleFactor * sensorScaleOffsetOverride;
var screenWidth = 0.82 * tabletWidth; var screenWidth = 0.82 * tabletWidth;
var screenHeight = 0.81 * tabletHeight; var screenHeight = 0.81 * tabletHeight;
@ -417,11 +418,13 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride)
var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; var homeButtonDim = 4.0 * tabletScaleFactor / 3.0;
Overlays.editOverlay(HMD.homeButtonID, { Overlays.editOverlay(HMD.homeButtonID, {
localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, 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 } dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }
}); });
Overlays.editOverlay(HMD.homeButtonHighlightID, { Overlays.editOverlay(HMD.homeButtonHighlightID, {
localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, 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 } dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }
}); });
}; };