diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6e5ed91bd7..ed8fe8b9b2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -548,6 +548,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_TABLET_VISIBLE_TO_OTHERS = false; Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) : QApplication(argc, argv), @@ -570,6 +571,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), + _tabletVisibleToOthersSetting("tabletVisibleToOthers", DEFAULT_TABLET_VISIBLE_TO_OTHERS), _constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true), _scaleMirror(1.0f), _rotateMirror(0.0f), @@ -2348,6 +2350,11 @@ void Application::setHmdTabletBecomesToolbarSetting(bool value) { updateSystemTabletMode(); } +void Application::setTabletVisibleToOthersSetting(bool value) { + _tabletVisibleToOthersSetting.set(value); + updateSystemTabletMode(); +} + void Application::setSettingConstrainToolbarPosition(bool setting) { _constrainToolbarPosition.set(setting); DependencyManager::get()->setConstrainToolbarToCenterX(setting); @@ -6903,5 +6910,10 @@ OverlayID Application::getTabletScreenID() const { OverlayID Application::getTabletHomeButtonID() const { auto HMD = DependencyManager::get(); - return HMD->getCurrentHomeButtonUUID(); + return HMD->getCurrentHomeButtonID(); +} + +QUuid Application::getTabletFrameID() const { + auto HMD = DependencyManager::get(); + return HMD->getCurrentTabletFrameID(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 662523ce1d..13c1458aee 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -218,6 +218,8 @@ public: void setDesktopTabletBecomesToolbarSetting(bool value); bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); } void setHmdTabletBecomesToolbarSetting(bool value); + bool getTabletVisibleToOthersSetting() { return _tabletVisibleToOthersSetting.get(); } + void setTabletVisibleToOthersSetting(bool value); float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); } void setSettingConstrainToolbarPosition(bool setting); @@ -300,6 +302,7 @@ public: OverlayID getTabletScreenID() const; OverlayID getTabletHomeButtonID() const; + QUuid getTabletFrameID() const; // may be an entity or an overlay signals: void svoImportRequested(const QString& url); @@ -561,6 +564,7 @@ private: Setting::Handle _desktopTabletScale; Setting::Handle _desktopTabletBecomesToolbarSetting; Setting::Handle _hmdTabletBecomesToolbarSetting; + Setting::Handle _tabletVisibleToOthersSetting; Setting::Handle _constrainToolbarPosition; float _scaleMirror; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 64ecb1dc29..f5fe82ef4b 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -933,6 +933,10 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const { } glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { + if (index < 0) { + index += numeric_limits::max() + 1; // 65536 + } + switch(index) { case SENSOR_TO_WORLD_MATRIX_INDEX: { glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); @@ -969,6 +973,10 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { } glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { + if (index < 0) { + index += numeric_limits::max() + 1; // 65536 + } + switch(index) { case SENSOR_TO_WORLD_MATRIX_INDEX: { glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1ea9891732..842939d938 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -823,7 +823,7 @@ void MyAvatar::saveData() { auto hmdInterface = DependencyManager::get(); _avatarEntitiesLock.withReadLock([&] { for (auto entityID : _avatarEntityData.keys()) { - if (hmdInterface->getCurrentTabletUIID() == entityID) { + if (hmdInterface->getCurrentTabletFrameID() == entityID) { // don't persist the tablet between domains / sessions continue; } @@ -2410,6 +2410,10 @@ glm::mat4 MyAvatar::computeCameraRelativeHandControllerMatrix(const glm::mat4& c } glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const { + if (index < 0) { + index += numeric_limits::max() + 1; // 65536 + } + switch (index) { case CONTROLLER_LEFTHAND_INDEX: { return getLeftHandControllerPoseInAvatarFrame().getRotation(); @@ -2443,6 +2447,10 @@ glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const { } glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const { + if (index < 0) { + index += numeric_limits::max() + 1; // 65536 + } + switch (index) { case CONTROLLER_LEFTHAND_INDEX: { return getLeftHandControllerPoseInAvatarFrame().getTranslation(); diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 463a21ded8..d895d5da4c 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -29,8 +29,8 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen Q_PROPERTY(glm::quat orientation READ getOrientation) Q_PROPERTY(bool mounted READ isMounted) Q_PROPERTY(bool showTablet READ getShouldShowTablet) - Q_PROPERTY(QUuid tabletID READ getCurrentTabletUIID WRITE setCurrentTabletUIID) - Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonUUID WRITE setCurrentHomeButtonUUID) + Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID) + Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonID WRITE setCurrentHomeButtonID) Q_PROPERTY(QUuid tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID) public: @@ -90,11 +90,11 @@ public: void setShouldShowTablet(bool value) { _showTablet = value; } bool getShouldShowTablet() const { return _showTablet; } - void setCurrentTabletUIID(QUuid tabletID) { _tabletUIID = tabletID; } - QUuid getCurrentTabletUIID() const { return _tabletUIID; } + void setCurrentTabletFrameID(QUuid tabletID) { _tabletUIID = tabletID; } + QUuid getCurrentTabletFrameID() const { return _tabletUIID; } - void setCurrentHomeButtonUUID(QUuid homeButtonID) { _homeButtonID = homeButtonID; } - QUuid getCurrentHomeButtonUUID() const { return _homeButtonID; } + void setCurrentHomeButtonID(QUuid homeButtonID) { _homeButtonID = homeButtonID; } + QUuid getCurrentHomeButtonID() const { return _homeButtonID; } void setCurrentTabletScreenID(QUuid tabletID) { _tabletScreenID = tabletID; } QUuid getCurrentTabletScreenID() const { return _tabletScreenID; } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index dd05d5c0e1..d291510556 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -102,7 +102,11 @@ void setupPreferences() { auto setter = [](bool value) { qApp->setHmdTabletBecomesToolbarSetting(value); }; preferences->addPreference(new CheckPreference(UI_CATEGORY, "HMD Tablet Becomes Toolbar", getter, setter)); } - + { + auto getter = []()->bool { return qApp->getTabletVisibleToOthersSetting(); }; + auto setter = [](bool value) { qApp->setTabletVisibleToOthersSetting(value); }; + preferences->addPreference(new CheckPreference(UI_CATEGORY, "Tablet Is Visible To Others", getter, setter)); + } // Snapshots static const QString SNAPSHOTS { "Snapshots" }; { diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index ff5177ed3a..10378ff858 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -39,7 +39,8 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : _isDashedLine(base3DOverlay->_isDashedLine), _ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection), _drawInFront(base3DOverlay->_drawInFront), - _isAA(base3DOverlay->_isAA) + _isAA(base3DOverlay->_isAA), + _isGrabbable(base3DOverlay->_isGrabbable) { setTransform(base3DOverlay->getTransform()); } @@ -59,15 +60,19 @@ QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& propert } else if (result["position"].isValid()) { glm::vec3 localPosition = SpatiallyNestable::worldToLocal(vec3FromVariant(result["position"]), parentID, parentJointIndex, success); - result["position"] = vec3toVariant(localPosition); + if (success) { + result["position"] = vec3toVariant(localPosition); + } } if (result["localOrientation"].isValid()) { result["orientation"] = result["localOrientation"]; } else if (result["orientation"].isValid()) { glm::quat localOrientation = SpatiallyNestable::worldToLocal(quatFromVariant(result["orientation"]), - parentID, parentJointIndex, success); - result["orientation"] = quatToVariant(localOrientation); + parentID, parentJointIndex, success); + if (success) { + result["orientation"] = quatToVariant(localOrientation); + } } return result; @@ -125,6 +130,11 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { needRenderItemUpdate = true; } + auto isGrabbable = properties["grabbable"]; + if (isGrabbable.isValid()) { + setIsGrabbable(isGrabbable.toBool()); + } + if (properties["position"].isValid()) { setLocalPosition(vec3FromVariant(properties["position"])); needRenderItemUpdate = true; @@ -227,6 +237,9 @@ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "drawInFront") { return _drawInFront; } + if (property == "grabbable") { + return _isGrabbable; + } if (property == "parentID") { return getParentID(); } diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 7906b9d6c0..a1c23e5cd8 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -38,6 +38,7 @@ public: bool getIsSolidLine() const { return !_isDashedLine; } bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; } bool getDrawInFront() const { return _drawInFront; } + bool getIsGrabbable() const { return _isGrabbable; } virtual bool isAA() const { return _isAA; } @@ -47,6 +48,7 @@ public: void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; } void setDrawInFront(bool value) { _drawInFront = value; } void setIsAA(bool value) { _isAA = value; } + void setIsGrabbable(bool value) { _isGrabbable = value; } virtual AABox getBounds() const override = 0; @@ -71,6 +73,7 @@ protected: bool _ignoreRayIntersection; bool _drawInFront; bool _isAA; + bool _isGrabbable { false }; }; #endif // hifi_Base3DOverlay_h diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 5913b2383d..a783f14102 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -406,11 +406,21 @@ RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray, const QScriptValue& overlayIDsToInclude, const QScriptValue& overlayIDsToDiscard, bool visibleOnly, bool collidableOnly) { - float bestDistance = std::numeric_limits::max(); - bool bestIsFront = false; const QVector overlaysToInclude = qVectorOverlayIDFromScriptValue(overlayIDsToInclude); const QVector overlaysToDiscard = qVectorOverlayIDFromScriptValue(overlayIDsToDiscard); + return findRayIntersectionInternal(ray, precisionPicking, + overlaysToInclude, overlaysToDiscard, visibleOnly, collidableOnly); +} + + +RayToOverlayIntersectionResult Overlays::findRayIntersectionInternal(const PickRay& ray, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly, bool collidableOnly) { + float bestDistance = std::numeric_limits::max(); + bool bestIsFront = false; + RayToOverlayIntersectionResult result; QMapIterator i(_overlaysWorld); i.toBack(); @@ -719,11 +729,41 @@ PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay r return pointerEvent; } + +RayToOverlayIntersectionResult Overlays::findRayIntersectionForMouseEvent(PickRay ray) { + QVector overlaysToInclude; + QVector overlaysToDiscard; + RayToOverlayIntersectionResult rayPickResult; + + // first priority is tablet screen + overlaysToInclude << qApp->getTabletScreenID(); + rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard); + if (rayPickResult.intersects) { + return rayPickResult; + } + // then tablet home button + overlaysToInclude.clear(); + overlaysToInclude << qApp->getTabletHomeButtonID(); + rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard); + if (rayPickResult.intersects) { + return rayPickResult; + } + // then tablet frame + overlaysToInclude.clear(); + overlaysToInclude << OverlayID(qApp->getTabletFrameID()); + rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard); + if (rayPickResult.intersects) { + return rayPickResult; + } + // then whatever + return findRayIntersection(ray); +} + void Overlays::mousePressEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mousePressEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray); + RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); if (rayPickResult.intersects) { _currentClickingOnOverlayID = rayPickResult.overlayID; @@ -744,7 +784,7 @@ void Overlays::mouseReleaseEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseReleaseEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray); + RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); if (rayPickResult.intersects) { // Only Web overlays can have focus. @@ -762,7 +802,7 @@ void Overlays::mouseMoveEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseMoveEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray); + RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); if (rayPickResult.intersects) { // Only Web overlays can have focus. @@ -803,3 +843,31 @@ void Overlays::mouseMoveEvent(QMouseEvent* event) { } } } + +QVector Overlays::findOverlays(const glm::vec3& center, float radius) const { + QVector result; + + QMapIterator i(_overlaysWorld); + i.toBack(); + while (i.hasPrevious()) { + i.previous(); + OverlayID thisID = i.key(); + auto overlay = std::dynamic_pointer_cast(i.value()); + if (overlay && overlay->getVisible() && !overlay->getIgnoreRayIntersection() && overlay->isLoaded()) { + // get AABox in frame of overlay + glm::vec3 dimensions = overlay->getDimensions(); + glm::vec3 low = dimensions * -0.5f; + AABox overlayFrameBox(low, dimensions); + + Transform overlayToWorldMatrix = overlay->getTransform(); + glm::mat4 worldToOverlayMatrix = glm::inverse(overlayToWorldMatrix.getMatrix()); + glm::vec3 overlayFrameSearchPosition = glm::vec3(worldToOverlayMatrix * glm::vec4(center, 1.0f)); + glm::vec3 penetration; + if (overlayFrameBox.findSpherePenetration(overlayFrameSearchPosition, radius, penetration)) { + result.append(thisID); + } + } + } + + return result; +} diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 7c6ba34f58..865df6309a 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -206,6 +206,16 @@ public slots: bool visibleOnly = false, bool collidableOnly = false); + /**jsdoc + * Return a list of 3d overlays with bounding boxes that touch the given sphere + * + * @function Overlays.findOverlays + * @param {Vec3} center the point to search from. + * @param {float} radius search radius + * @return {List of Overlays.OverlayID} list of overlays withing the radius + */ + QVector findOverlays(const glm::vec3& center, float radius) const; + /**jsdoc * Check whether an overlay's assets have been loaded. For example, if the * overlay is an "image" overlay, this will indicate whether the its image @@ -317,6 +327,12 @@ private: OverlayID _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID }; OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID }; + + RayToOverlayIntersectionResult findRayIntersectionInternal(const PickRay& ray, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly = false, bool collidableOnly = false); + RayToOverlayIntersectionResult findRayIntersectionForMouseEvent(PickRay ray); }; #endif // hifi_Overlays_h diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp index f38d24c31f..c8a7b61aa7 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -16,8 +16,8 @@ void UserActivityLoggerScriptingInterface::enabledEdit() { logAction("enabled_edit"); } -void UserActivityLoggerScriptingInterface::openedTablet() { - logAction("opened_tablet"); +void UserActivityLoggerScriptingInterface::openedTablet(bool visibleToOthers) { + logAction("opened_tablet", { { "visible_to_others", visibleToOthers } }); } void UserActivityLoggerScriptingInterface::closedTablet() { diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.h b/libraries/networking/src/UserActivityLoggerScriptingInterface.h index b827b2262a..cf38450891 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.h +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.h @@ -21,7 +21,7 @@ class UserActivityLoggerScriptingInterface : public QObject, public Dependency { Q_OBJECT public: Q_INVOKABLE void enabledEdit(); - Q_INVOKABLE void openedTablet(); + Q_INVOKABLE void openedTablet(bool visibleToOthers); Q_INVOKABLE void closedTablet(); Q_INVOKABLE void openedMarketplace(); Q_INVOKABLE void toggledAway(bool isAway); diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index ad4b605585..015cbf1221 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -13,7 +13,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html /* global getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings, - Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, + Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, setGrabCommunications, Menu, HMD, isInEditMode */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ @@ -399,7 +399,7 @@ function entityHasActions(entityID) { function findRayIntersection(pickRay, precise, include, exclude) { var entities = Entities.findRayIntersection(pickRay, precise, include, exclude, true); - var overlays = Overlays.findRayIntersection(pickRay); + var overlays = Overlays.findRayIntersection(pickRay, precise, [], [HMD.tabletID]); if (!overlays.intersects || (entities.intersects && (entities.distance <= overlays.distance))) { return entities; } @@ -644,6 +644,7 @@ EquipHotspotBuddy.prototype.updateHotspot = function(hotspot, timestamp) { // override default sphere with a user specified model, if it exists. overlayInfoSet.overlays.push(Overlays.addOverlay("model", { + name: "hotspot overlay", url: hotspot.modelURL ? hotspot.modelURL : DEFAULT_SPHERE_MODEL_URL, position: hotspot.worldPosition, rotation: { @@ -777,7 +778,7 @@ function MyController(hand) { }; this.actionID = null; // action this script created... - this.grabbedEntity = null; // on this entity. + this.grabbedThingID = null; // on this entity. this.grabbedOverlay = null; this.state = STATE_OFF; this.pointer = null; // entity-id of line object @@ -854,8 +855,11 @@ function MyController(hand) { }; this.callEntityMethodOnGrabbed = function(entityMethodName) { + if (this.grabbedIsOverlay) { + return; + } var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedEntity, entityMethodName, args); + Entities.callEntityMethod(this.grabbedThingID, entityMethodName, args); }; this.setState = function(newState, reason) { @@ -906,6 +910,7 @@ function MyController(hand) { if (!this.grabPointSphere) { this.grabPointSphere = Overlays.addOverlay("sphere", { + name: "grabPointSphere", localPosition: getGrabPointSphereOffset(this.handToController()), localRotation: { x: 0, y: 0, z: 0, w: 1 }, dimensions: GRAB_POINT_SPHERE_RADIUS * 2, @@ -936,6 +941,7 @@ function MyController(hand) { var brightColor = colorPow(color, 0.06); if (this.searchSphere === null) { var sphereProperties = { + name: "searchSphere", position: location, rotation: rotation, outerRadius: size * 1.2, @@ -958,7 +964,8 @@ function MyController(hand) { innerAlpha: 1.0, outerAlpha: 0.0, outerRadius: size * 1.2, - visible: true + visible: true, + ignoreRayIntersection: true }); } }; @@ -969,6 +976,7 @@ function MyController(hand) { } var stylusProperties = { + name: "stylus", url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx", localPosition: Vec3.sum({ x: 0.0, y: WEB_TOUCH_Y_OFFSET, @@ -1003,6 +1011,7 @@ function MyController(hand) { this.overlayLineOn = function(closePoint, farPoint, color) { if (this.overlayLine === null) { var lineProperties = { + name: "line", glow: 1.0, start: closePoint, end: farPoint, @@ -1178,6 +1187,13 @@ function MyController(hand) { } } + var candidateOverlays = Overlays.findOverlays(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE); + for (var j = 0; j < candidateOverlays.length; j++) { + if (this.isTablet(candidateOverlays[j])) { + nearWeb = true; + } + } + if (nearWeb) { this.showStylus(); var rayPickInfo = this.calcRayPickInfo(this.hand); @@ -1588,7 +1604,7 @@ function MyController(hand) { var farSearching = this.triggerSmoothedSqueezed() && (Date.now() - this.searchStartTime > FAR_SEARCH_DELAY); - this.grabbedEntity = null; + this.grabbedThingID = null; this.grabbedOverlay = null; this.isInitialGrab = false; this.preparingHoldRelease = false; @@ -1596,7 +1612,7 @@ function MyController(hand) { this.checkForUnexpectedChildren(); if ((this.triggerSmoothedReleased() && this.secondaryReleased())) { - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "trigger released"); return; } @@ -1617,8 +1633,9 @@ function MyController(hand) { if (potentialEquipHotspot) { if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && holdEnabled) { this.grabbedHotspot = potentialEquipHotspot; - this.grabbedEntity = potentialEquipHotspot.entityID; - this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'"); + this.grabbedThingID = potentialEquipHotspot.entityID; + this.grabbedIsOverlay = false; + this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedThingID).name + "'"); return; } @@ -1629,6 +1646,11 @@ function MyController(hand) { return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); }); + var candidateOverlays = Overlays.findOverlays(handPosition, NEAR_GRAB_RADIUS); + var grabbableOverlays = candidateOverlays.filter(function(overlayID) { + return Overlays.getProperty(overlayID, "grabbable"); + }); + if (rayPickInfo.entityID) { this.intersectionDistance = rayPickInfo.distance; if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { @@ -1640,6 +1662,23 @@ function MyController(hand) { this.intersectionDistance = 0; } + if (grabbableOverlays.length > 0) { + grabbableOverlays.sort(function(a, b) { + var aPosition = Overlays.getProperty(a, "position"); + var aDistance = Vec3.distance(aPosition, handPosition); + var bPosition = Overlays.getProperty(a, "position"); + var bDistance = Vec3.distance(bPosition, handPosition); + return aDistance - bDistance; + }); + this.grabbedThingID = grabbableOverlays[0]; + this.grabbedIsOverlay = true; + if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) { + this.setState(STATE_NEAR_GRABBING, "near grab overlay '" + + Overlays.getProperty(this.grabbedThingID, "name") + "'"); + return; + } + } + var entity; if (grabbableEntities.length > 0) { // sort by distance @@ -1649,23 +1688,20 @@ function MyController(hand) { return aDistance - bDistance; }); entity = grabbableEntities[0]; - if (!isInEditMode() || entity == HMD.tabletID) { + if (!isInEditMode() || entity == HMD.tabletID) { // tablet is grabbable, even when editing name = entityPropertiesCache.getProps(entity).name; - this.grabbedEntity = entity; + this.grabbedThingID = entity; + this.grabbedIsOverlay = false; if (this.entityWantsTrigger(entity)) { if (this.triggerSmoothedGrab()) { this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'"); return; - } else { - // potentialNearTriggerEntity = entity; } } else { // If near something grabbable, grab it! if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) { - this.setState(STATE_NEAR_GRABBING, "near grab '" + name + "'"); + this.setState(STATE_NEAR_GRABBING, "near grab entity '" + name + "'"); return; - } else { - // potentialNearGrabEntity = entity; } } } @@ -1701,7 +1737,8 @@ function MyController(hand) { name = entityPropertiesCache.getProps(entity).name; if (this.entityWantsTrigger(entity)) { if (this.triggerSmoothedGrab()) { - this.grabbedEntity = entity; + this.grabbedThingID = entity; + this.grabbedIsOverlay = false; this.setState(STATE_FAR_TRIGGER, "far trigger '" + name + "'"); return; } else { @@ -1709,7 +1746,8 @@ function MyController(hand) { } } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { - this.grabbedEntity = entity; + this.grabbedThingID = entity; + this.grabbedIsOverlay = false; this.grabbedDistance = rayPickInfo.distance; this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); return; @@ -1789,7 +1827,8 @@ function MyController(hand) { Entities.sendHoverOverEntity(entity, pointerEvent); } - this.grabbedEntity = entity; + this.grabbedThingID = entity; + this.grabbedIsOverlay = false; this.setState(STATE_ENTITY_STYLUS_TOUCHING, "begin touching entity '" + name + "'"); return true; @@ -1917,7 +1956,8 @@ function MyController(hand) { } if (this.triggerSmoothedGrab() && (!isEditing() || this.isTablet(entity))) { - this.grabbedEntity = entity; + this.grabbedThingID = entity; + this.grabbedIsOverlay = false; this.setState(STATE_ENTITY_LASER_TOUCHING, "begin touching entity '" + name + "'"); return true; } @@ -2028,7 +2068,7 @@ function MyController(hand) { var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); var now = Date.now(); // add the action and initialize some variables @@ -2058,7 +2098,7 @@ function MyController(hand) { var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject); this.actionID = NULL_UUID; - this.actionID = Entities.addAction("spring", this.grabbedEntity, { + this.actionID = Entities.addAction("spring", this.grabbedThingID, { targetPosition: this.currentObjectPosition, linearTimeScale: timeScale, targetRotation: this.currentObjectRotation, @@ -2083,12 +2123,12 @@ function MyController(hand) { this.ensureDynamic = function() { // if we distance hold something and keep it very still before releasing it, it ends up // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. - var props = Entities.getEntityProperties(this.grabbedEntity, ["velocity", "dynamic", "parentID"]); + var props = Entities.getEntityProperties(this.grabbedThingID, ["velocity", "dynamic", "parentID"]); if (props.dynamic && props.parentID == NULL_UUID) { var velocity = props.velocity; if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD velocity = { x: 0.0, y: 0.2, z:0.0 }; - Entities.editEntity(this.grabbedEntity, { velocity: velocity }); + Entities.editEntity(this.grabbedThingID, { velocity: velocity }); } } }; @@ -2110,7 +2150,7 @@ function MyController(hand) { var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); var now = Date.now(); var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds @@ -2165,7 +2205,7 @@ function MyController(hand) { newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); var objectToAvatar = Vec3.subtract(this.currentObjectPosition, MyAvatar.position); - var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData); + var handControllerData = getEntityCustomData('handControllerKey', this.grabbedThingID, defaultMoveWithHeadData); if (handControllerData.disableMoveWithHead !== true) { // mix in head motion if (MOVE_WITH_HEAD) { @@ -2194,7 +2234,7 @@ function MyController(hand) { this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD); var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); - var success = Entities.updateAction(this.grabbedEntity, this.actionID, { + var success = Entities.updateAction(this.grabbedThingID, this.actionID, { targetPosition: newTargetPosition, linearTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject), targetRotation: this.currentObjectRotation, @@ -2211,7 +2251,7 @@ function MyController(hand) { }; this.setupHoldAction = function() { - this.actionID = Entities.addAction("hold", this.grabbedEntity, { + this.actionID = Entities.addAction("hold", this.grabbedThingID, { hand: this.hand === RIGHT_HAND ? "right" : "left", timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, relativePosition: this.offsetPosition, @@ -2301,17 +2341,30 @@ function MyController(hand) { Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); if (this.entityActivated) { - var saveGrabbedID = this.grabbedEntity; + var saveGrabbedID = this.grabbedThingID; this.release(); - this.grabbedEntity = saveGrabbedID; + this.grabbedThingID = saveGrabbedID; } - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - if (FORCE_IGNORE_IK) { + var grabbedProperties; + if (this.grabbedIsOverlay) { + grabbedProperties = { + position: Overlays.getProperty(this.grabbedThingID, "position"), + rotation: Overlays.getProperty(this.grabbedThingID, "rotation"), + parentID: Overlays.getProperty(this.grabbedThingID, "parentID"), + parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"), + dynamic: false, + shapeType: "none" + }; this.ignoreIK = true; } else { - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; + grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); + if (FORCE_IGNORE_IK) { + this.ignoreIK = true; + } else { + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedThingID, DEFAULT_GRABBABLE_DATA); + this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; + } } var handRotation; @@ -2350,7 +2403,8 @@ function MyController(hand) { this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); } - var isPhysical = propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity); + var isPhysical = propsArePhysical(grabbedProperties) || + (!this.grabbedIsOverlay && entityHasActions(this.grabbedThingID)); if (isPhysical && this.state == STATE_NEAR_GRABBING && grabbedProperties.parentID === NULL_UUID) { // grab entity via action if (!this.setupHoldAction()) { @@ -2358,7 +2412,7 @@ function MyController(hand) { } Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'grab', - grabbedEntity: this.grabbedEntity, + grabbedEntity: this.grabbedThingID, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); } else { @@ -2383,29 +2437,36 @@ function MyController(hand) { reparentProps.localPosition = this.offsetPosition; reparentProps.localRotation = this.offsetRotation; } - Entities.editEntity(this.grabbedEntity, reparentProps); + + if (this.grabbedIsOverlay) { + Overlays.editOverlay(this.grabbedThingID, reparentProps); + } else { + Entities.editEntity(this.grabbedThingID, reparentProps); + } if (this.thisHandIsParent(grabbedProperties)) { // this should never happen, but if it does, don't set previous parent to be this hand. - // this.previousParentID[this.grabbedEntity] = NULL; - // this.previousParentJointIndex[this.grabbedEntity] = -1; + // this.previousParentID[this.grabbedThingID] = NULL; + // this.previousParentJointIndex[this.grabbedThingID] = -1; } else { - this.previousParentID[this.grabbedEntity] = grabbedProperties.parentID; - this.previousParentJointIndex[this.grabbedEntity] = grabbedProperties.parentJointIndex; + this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID; + this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex; } Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'equip', - grabbedEntity: this.grabbedEntity, + grabbedEntity: this.grabbedThingID, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); } - Entities.editEntity(this.grabbedEntity, { - velocity: { x: 0, y: 0, z: 0 }, - angularVelocity: { x: 0, y: 0, z: 0 }, - // dynamic: false - }); + if (!this.grabbedIsOverlay) { + Entities.editEntity(this.grabbedThingID, { + velocity: { x: 0, y: 0, z: 0 }, + angularVelocity: { x: 0, y: 0, z: 0 }, + // dynamic: false + }); + } if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("startNearGrab"); @@ -2471,26 +2532,39 @@ function MyController(hand) { if (dropDetected && !this.waitForTriggerRelease && this.triggerSmoothedGrab()) { // store the offset attach points into preferences. - if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedEntity) { - var prefprops = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedThingID) { + var prefprops = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "localRotation"]); if (prefprops && prefprops.localPosition && prefprops.localRotation) { storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, prefprops.localPosition, prefprops.localRotation); } } - var grabbedEntity = this.grabbedEntity; + var grabbedEntity = this.grabbedThingID; this.release(); - this.grabbedEntity = grabbedEntity; + this.grabbedThingID = grabbedEntity; this.setState(STATE_NEAR_GRABBING, "drop gesture detected"); return; } this.prevDropDetected = dropDetected; } - var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "parentJointIndex", + var props; + if (this.grabbedIsOverlay) { + props = { + localPosition: Overlays.getProperty(this.grabbedThingID, "localPosition"), + parentID: Overlays.getProperty(this.grabbedThingID, "parentID"), + parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"), + position: Overlays.getProperty(this.grabbedThingID, "position"), + rotation: Overlays.getProperty(this.grabbedThingID, "rotation"), + dimensions: Overlays.getProperty(this.grabbedThingID, "dimensions"), + registrationPoint: { x: 0.5, y: 0.5, z: 0.5 } + }; + } else { + props = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "parentID", "parentJointIndex", "position", "rotation", "dimensions", "registrationPoint"]); + } if (!props.position) { // server may have reset, taking our equipped entity with it. move back to "off" state this.callEntityMethodOnGrabbed("releaseGrab"); @@ -2502,7 +2576,7 @@ function MyController(hand) { // someone took it from us or otherwise edited the parentID. end the grab. We don't do this // for equipped things so that they can be adjusted while equipped. this.callEntityMethodOnGrabbed("releaseGrab"); - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "someone took it"); return; } @@ -2584,7 +2658,7 @@ function MyController(hand) { if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSECS_PER_SEC) { // if less than a 5 seconds left, refresh the actions ttl - var success = Entities.updateAction(this.grabbedEntity, this.actionID, { + var success = Entities.updateAction(this.grabbedThingID, this.actionID, { hand: this.hand === RIGHT_HAND ? "right" : "left", timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, relativePosition: this.offsetPosition, @@ -2598,14 +2672,14 @@ function MyController(hand) { this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); } else { print("continueNearGrabbing -- updateAction failed"); - Entities.deleteAction(this.grabbedEntity, this.actionID); + Entities.deleteAction(this.grabbedThingID, this.actionID); this.setupHoldAction(); } } }; this.maybeScale = function(props) { - if (!objectScalingEnabled || this.isTablet(this.grabbedEntity)) { + if (!objectScalingEnabled || this.isTablet(this.grabbedThingID) || this.grabbedIsOverlay) { return; } @@ -2627,7 +2701,7 @@ function MyController(hand) { this.getOtherHandController().getHandPosition())); var currentRescale = scalingCurrentDistance / this.scalingStartDistance; var newDimensions = Vec3.multiply(currentRescale, this.scalingStartDimensions); - Entities.editEntity(this.grabbedEntity, { dimensions: newDimensions }); + Entities.editEntity(this.grabbedThingID, { dimensions: newDimensions }); } }; @@ -2678,7 +2752,7 @@ function MyController(hand) { this.nearTrigger = function(deltaTime, timestamp) { if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("stopNearTrigger"); - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "trigger released"); return; } @@ -2688,7 +2762,7 @@ function MyController(hand) { this.farTrigger = function(deltaTime, timestamp) { if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("stopFarTrigger"); - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "trigger released"); return; } @@ -2703,9 +2777,9 @@ function MyController(hand) { var intersection = findRayIntersection(pickRay, true, [], [], true); if (intersection.accurate || intersection.overlayID) { this.lastPickTime = now; - if (intersection.entityID != this.grabbedEntity) { + if (intersection.entityID != this.grabbedThingID) { this.callEntityMethodOnGrabbed("stopFarTrigger"); - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "laser moved off of entity"); return; } @@ -2727,13 +2801,13 @@ function MyController(hand) { this.entityTouchingEnter = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, + var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, getControllerWorldLocation(this.handToController(), true)); if (intersectInfo) { var pointerEvent = { type: "Press", id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point), + pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point), pos3D: intersectInfo.point, normal: intersectInfo.normal, direction: intersectInfo.searchRay.direction, @@ -2741,8 +2815,8 @@ function MyController(hand) { isPrimaryHeld: true }; - Entities.sendMousePressOnEntity(this.grabbedEntity, pointerEvent); - Entities.sendClickDownOnEntity(this.grabbedEntity, pointerEvent); + Entities.sendMousePressOnEntity(this.grabbedThingID, pointerEvent); + Entities.sendClickDownOnEntity(this.grabbedThingID, pointerEvent); this.touchingEnterTimer = 0; this.touchingEnterPointerEvent = pointerEvent; @@ -2764,7 +2838,7 @@ function MyController(hand) { this.entityTouchingExit = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, + var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, getControllerWorldLocation(this.handToController(), true)); if (intersectInfo) { var pointerEvent; @@ -2772,7 +2846,7 @@ function MyController(hand) { pointerEvent = { type: "Release", id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point), + pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point), pos3D: intersectInfo.point, normal: intersectInfo.normal, direction: intersectInfo.searchRay.direction, @@ -2785,11 +2859,11 @@ function MyController(hand) { pointerEvent.isPrimaryHeld = false; } - Entities.sendMouseReleaseOnEntity(this.grabbedEntity, pointerEvent); - Entities.sendClickReleaseOnEntity(this.grabbedEntity, pointerEvent); - Entities.sendHoverLeaveEntity(this.grabbedEntity, pointerEvent); + Entities.sendMouseReleaseOnEntity(this.grabbedThingID, pointerEvent); + Entities.sendClickReleaseOnEntity(this.grabbedThingID, pointerEvent); + Entities.sendHoverLeaveEntity(this.grabbedThingID, pointerEvent); } - this.grabbedEntity = null; + this.grabbedThingID = null; this.grabbedOverlay = null; }; @@ -2797,7 +2871,7 @@ function MyController(hand) { this.touchingEnterTimer += dt; - entityPropertiesCache.addEntity(this.grabbedEntity); + entityPropertiesCache.addEntity(this.grabbedThingID); if (this.state == STATE_ENTITY_LASER_TOUCHING && !this.triggerSmoothedGrab()) { this.setState(STATE_OFF, "released trigger"); @@ -2805,7 +2879,7 @@ function MyController(hand) { } // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, + var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, getControllerWorldLocation(this.handToController(), true)); if (intersectInfo) { @@ -2815,15 +2889,15 @@ function MyController(hand) { return; } - if (Entities.keyboardFocusEntity != this.grabbedEntity) { + if (Entities.keyboardFocusEntity != this.grabbedThingID) { Overlays.keyboardFocusOverlay = 0; - Entities.keyboardFocusEntity = this.grabbedEntity; + Entities.keyboardFocusEntity = this.grabbedThingID; } var pointerEvent = { type: "Move", id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point), + pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point), pos3D: intersectInfo.point, normal: intersectInfo.normal, direction: intersectInfo.searchRay.direction, @@ -2834,8 +2908,8 @@ function MyController(hand) { var POINTER_PRESS_TO_MOVE_DELAY = 0.25; // seconds if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || Vec3.distance(intersectInfo.point, this.touchingEnterPointerEvent.pos3D) > this.deadspotRadius) { - Entities.sendMouseMoveOnEntity(this.grabbedEntity, pointerEvent); - Entities.sendHoldingClickOnEntity(this.grabbedEntity, pointerEvent); + Entities.sendMouseMoveOnEntity(this.grabbedThingID, pointerEvent); + Entities.sendHoldingClickOnEntity(this.grabbedThingID, pointerEvent); this.deadspotExpired = true; } @@ -2845,7 +2919,7 @@ function MyController(hand) { } Reticle.setVisible(false); } else { - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "grabbed entity was destroyed"); return; } @@ -2930,7 +3004,7 @@ function MyController(hand) { Overlays.sendMouseReleaseOnOverlay(this.grabbedOverlay, pointerEvent); Overlays.sendHoverLeaveOverlay(this.grabbedOverlay, pointerEvent); } - this.grabbedEntity = null; + this.grabbedThingID = null; this.grabbedOverlay = null; }; @@ -2953,7 +3027,7 @@ function MyController(hand) { if (this.state == STATE_OVERLAY_STYLUS_TOUCHING && intersectInfo.distance > WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET + WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE) { - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "pulled away from overlay"); return; } @@ -3010,7 +3084,7 @@ function MyController(hand) { } Reticle.setVisible(false); } else { - this.grabbedEntity = null; + this.grabbedThingID = null; this.setState(STATE_OFF, "grabbed overlay was destroyed"); return; } @@ -3019,7 +3093,7 @@ function MyController(hand) { this.release = function() { this.turnOffVisualizations(); - if (this.grabbedEntity !== null) { + if (this.grabbedThingID !== null) { if (this.state === STATE_HOLD) { this.callEntityMethodOnGrabbed("releaseEquip"); } @@ -3027,35 +3101,49 @@ function MyController(hand) { // Make a small release haptic pulse if we really were holding something Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); if (this.actionID !== null) { - Entities.deleteAction(this.grabbedEntity, this.actionID); + Entities.deleteAction(this.grabbedThingID, this.actionID); } else { // no action, so it's a parenting grab - if (this.previousParentID[this.grabbedEntity] === NULL_UUID) { - Entities.editEntity(this.grabbedEntity, { - parentID: this.previousParentID[this.grabbedEntity], - parentJointIndex: this.previousParentJointIndex[this.grabbedEntity] - }); - this.ensureDynamic(); + if (this.previousParentID[this.grabbedThingID] === NULL_UUID) { + if (this.grabbedIsOverlay) { + Overlays.editOverlay(this.grabbedThingID, { + parentID: NULL_UUID, + parentJointIndex: -1 + }); + } else { + Entities.editEntity(this.grabbedThingID, { + parentID: this.previousParentID[this.grabbedThingID], + parentJointIndex: this.previousParentJointIndex[this.grabbedThingID] + }); + this.ensureDynamic(); + } } else { - // we're putting this back as a child of some other parent, so zero its velocity - Entities.editEntity(this.grabbedEntity, { - parentID: this.previousParentID[this.grabbedEntity], - parentJointIndex: this.previousParentJointIndex[this.grabbedEntity], - velocity: {x: 0, y: 0, z: 0}, - angularVelocity: {x: 0, y: 0, z: 0} - }); + if (this.grabbedIsOverlay) { + Overlays.editOverlay(this.grabbedThingID, { + parentID: this.previousParentID[this.grabbedThingID], + parentJointIndex: this.previousParentJointIndex[this.grabbedThingID], + }); + } else { + // we're putting this back as a child of some other parent, so zero its velocity + Entities.editEntity(this.grabbedThingID, { + parentID: this.previousParentID[this.grabbedThingID], + parentJointIndex: this.previousParentJointIndex[this.grabbedThingID], + velocity: {x: 0, y: 0, z: 0}, + angularVelocity: {x: 0, y: 0, z: 0} + }); + } } } Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'release', - grabbedEntity: this.grabbedEntity, + grabbedEntity: this.grabbedThingID, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); } this.actionID = null; - this.grabbedEntity = null; + this.grabbedThingID = null; this.grabbedOverlay = null; this.grabbedHotspot = null; diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 367ef05aea..c4b41bcab5 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -7,8 +7,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global getControllerWorldLocation, setEntityCustomData, Tablet, WebTablet:true, HMD, Settings, Script, - Vec3, Quat, MyAvatar, Entities, Overlays, Camera, Messages, Xform, clamp */ +/* global getControllerWorldLocation, Tablet, WebTablet:true, HMD, Settings, Script, + Vec3, Quat, MyAvatar, Entities, Overlays, Camera, Messages, Xform, clamp, Controller, Mat4 */ Script.include(Script.resolvePath("../libraries/utils.js")); Script.include(Script.resolvePath("../libraries/controllers.js")); @@ -116,6 +116,8 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { name: "WebTablet Tablet", type: "Model", modelURL: TABLET_MODEL_PATH, + url: TABLET_MODEL_PATH, // for overlay + grabbable: true, // for overlay userData: JSON.stringify({ "grabbableKey": {"grabbable": true} }), @@ -127,7 +129,14 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { this.calculateTabletAttachmentProperties(hand, true, tabletProperties); this.cleanUpOldTablets(); - this.tabletEntityID = Entities.addEntity(tabletProperties, clientOnly); + + if (Settings.getValue("tabletVisibleToOthers")) { + this.tabletEntityID = Entities.addEntity(tabletProperties, clientOnly); + this.tabletIsOverlay = false; + } else { + this.tabletEntityID = Overlays.addOverlay("model", tabletProperties); + this.tabletIsOverlay = true; + } if (this.webOverlayID) { Overlays.deleteOverlay(this.webOverlayID); @@ -152,7 +161,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { }); var HOME_BUTTON_Y_OFFSET = (this.height / 2) - 0.035; - this.homeButtonEntity = Overlays.addOverlay("sphere", { + this.homeButtonID = Overlays.addOverlay("sphere", { name: "homeButton", localPosition: {x: 0.0, y: -HOME_BUTTON_Y_OFFSET, z: -0.01}, localRotation: Quat.angleAxis(0, Y_AXIS), @@ -165,7 +174,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { }); this.receive = function (channel, senderID, senderUUID, localOnly) { - if (_this.homeButtonEntity == senderID) { + if (_this.homeButtonID == senderID) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var onHomeScreen = tablet.onHomeScreen(); if (onHomeScreen) { @@ -184,7 +193,16 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { }; this.getLocation = function() { - return Entities.getEntityProperties(_this.tabletEntityID, ["localPosition", "localRotation"]); + if (this.tabletIsOverlay) { + var location = Overlays.getProperty(this.tabletEntityID, "localPosition"); + var orientation = Overlays.getProperty(this.tabletEntityID, "localOrientation"); + return { + localPosition: location, + localRotation: orientation + }; + } else { + return Entities.getEntityProperties(_this.tabletEntityID, ["localPosition", "localRotation"]); + } }; this.clicked = false; @@ -242,8 +260,12 @@ WebTablet.prototype.getOverlayObject = function () { WebTablet.prototype.destroy = function () { Overlays.deleteOverlay(this.webOverlayID); - Entities.deleteEntity(this.tabletEntityID); - Overlays.deleteOverlay(this.homeButtonEntity); + if (this.tabletIsOverlay) { + Overlays.deleteOverlay(this.tabletEntityID); + } else { + Entities.deleteEntity(this.tabletEntityID); + } + Overlays.deleteOverlay(this.homeButtonID); HMD.displayModeChanged.disconnect(this.myOnHmdChanged); Controller.mousePressEvent.disconnect(this.myMousePressEvent); @@ -426,10 +448,16 @@ WebTablet.prototype.getPosition = function () { WebTablet.prototype.mousePressEvent = function (event) { var pickRay = Camera.computePickRay(event.x, event.y); - var entityPickResults = Entities.findRayIntersection(pickRay, true, [this.tabletEntityID]); // non-accurate picking - if (entityPickResults.intersects && entityPickResults.entityID === this.tabletEntityID) { - var overlayPickResults = Overlays.findRayIntersection(pickRay); - if (overlayPickResults.intersects && overlayPickResults.overlayID === HMD.homeButtonID) { + var entityPickResults; + if (this.tabletIsOverlay) { + entityPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]); + } else { + entityPickResults = Entities.findRayIntersection(pickRay, true, [this.tabletEntityID]); + } + if (entityPickResults.intersects && (entityPickResults.entityID === this.tabletEntityID || + entityPickResults.overlayID === this.tabletEntityID)) { + var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.webOverlayID, this.homeButtonID], []); + if (overlayPickResults.intersects && overlayPickResults.overlayID === this.homeButtonID) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var onHomeScreen = tablet.onHomeScreen(); if (onHomeScreen) { @@ -438,11 +466,15 @@ WebTablet.prototype.mousePressEvent = function (event) { tablet.gotoHomeScreen(); this.setHomeButtonTexture(); } - } else if (!HMD.active && (!overlayPickResults.intersects || !overlayPickResults.overlayID === this.webOverlayID)) { + } else if (!HMD.active && (!overlayPickResults.intersects || overlayPickResults.overlayID !== this.webOverlayID)) { this.dragging = true; var invCameraXform = new Xform(Camera.orientation, Camera.position).inv(); this.initialLocalIntersectionPoint = invCameraXform.xformPoint(entityPickResults.intersection); - this.initialLocalPosition = Entities.getEntityProperties(this.tabletEntityID, ["localPosition"]).localPosition; + if (this.tabletIsOverlay) { + this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition"); + } else { + this.initialLocalPosition = Entities.getEntityProperties(this.tabletEntityID, ["localPosition"]).localPosition; + } } } }; @@ -488,9 +520,15 @@ WebTablet.prototype.mouseMoveEvent = function (event) { var localIntersectionPoint = Vec3.sum(localPickRay.origin, Vec3.multiply(localPickRay.direction, result.distance)); var localOffset = Vec3.subtract(localIntersectionPoint, this.initialLocalIntersectionPoint); var localPosition = Vec3.sum(this.initialLocalPosition, localOffset); - Entities.editEntity(this.tabletEntityID, { - localPosition: localPosition - }); + if (this.tabletIsOverlay) { + Overlays.editOverlay(this.tabletEntityID, { + localPosition: localPosition + }); + } else { + Entities.editEntity(this.tabletEntityID, { + localPosition: localPosition + }); + } } } }; diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index d6cef5925a..31d069442f 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -31,7 +31,7 @@ UIWebTablet = new WebTablet("qml/hifi/tablet/TabletRoot.qml", DEFAULT_WIDTH * (HMD_TABLET_SCALE / 100), null, activeHand, true); UIWebTablet.register(); HMD.tabletID = UIWebTablet.tabletEntityID; - HMD.homeButtonID = UIWebTablet.homeButtonEntity; + HMD.homeButtonID = UIWebTablet.homeButtonID; HMD.tabletScreenID = UIWebTablet.webOverlayID; } @@ -79,7 +79,7 @@ hideTabletUI(); HMD.closeTablet(); } else if (HMD.showTablet && !tabletShown && !toolbarMode) { - UserActivityLogger.openedTablet(); + UserActivityLogger.openedTablet(Settings.getValue("tabletVisibleToOthers")); showTabletUI(); } else if (!HMD.showTablet && tabletShown) { UserActivityLogger.closedTablet(); @@ -128,5 +128,6 @@ Entities.deleteEntity(HMD.tabletID); HMD.tabletID = null; HMD.homeButtonID = null; + HMD.tabletScreenID = null; }); }()); // END LOCAL_SCOPE