diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 5855306994..94d0024fd5 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -3277,4 +3277,8 @@ void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properti properties.setEntityHostType(getEntityHostType()); properties.setOwningAvatarID(getOwningAvatarID()); setLastBroadcast(now); // for debug/physics status icons +} + +bool EntityItem::isWearable() const { + return isVisible() && (getParentID() == DependencyManager::get()->getSessionUUID() || getParentID() == AVATAR_SELF_ID); } \ No newline at end of file diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 8c78dd1cd6..eb1cdf24e4 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -489,7 +489,7 @@ public: void scriptHasUnloaded(); void setScriptHasFinishedPreload(bool value); bool isScriptPreloadFinished(); - + virtual bool isWearable() const; bool isDomainEntity() const { return _hostType == entity::HostType::DOMAIN; } bool isAvatarEntity() const { return _hostType == entity::HostType::AVATAR; } bool isLocalEntity() const { return _hostType == entity::HostType::LOCAL; } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 4a634899c4..1b1853762b 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -114,6 +114,8 @@ bool EntityScriptingInterface::canReplaceContent() { void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { if (_entityTree) { + disconnect(_entityTree.get(), &EntityTree::addingEntityPointer, this, &EntityScriptingInterface::onAddingEntity); + disconnect(_entityTree.get(), &EntityTree::deletingEntityPointer, this, &EntityScriptingInterface::onDeletingEntity); disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); disconnect(_entityTree.get(), &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); disconnect(_entityTree.get(), &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); @@ -122,6 +124,8 @@ void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { _entityTree = elementTree; if (_entityTree) { + connect(_entityTree.get(), &EntityTree::addingEntityPointer, this, &EntityScriptingInterface::onAddingEntity, Qt::DirectConnection); + connect(_entityTree.get(), &EntityTree::deletingEntityPointer, this, &EntityScriptingInterface::onDeletingEntity, Qt::DirectConnection); connect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); connect(_entityTree.get(), &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); connect(_entityTree.get(), &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); @@ -1060,7 +1064,17 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer } } +void EntityScriptingInterface::onAddingEntity(EntityItem* entity) { + if (entity->isWearable()) { + emit addingWearable(entity->getEntityItemID()); + } +} +void EntityScriptingInterface::onDeletingEntity(EntityItem* entity) { + if (entity->isWearable()) { + emit deletingWearable(entity->getEntityItemID()); + } +} QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const { PROFILE_RANGE(script_entities, __FUNCTION__); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index ff1149fb06..0cea005ddd 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -1908,6 +1908,31 @@ signals: */ void addingEntity(const EntityItemID& entityID); + /**jsdoc + * Triggered when an 'wearable' entity is deleted. + * @function Entities.deletingWearable + * @param {Uuid} entityID - The ID of the 'wearable' entity deleted. + * @returns {Signal} + * @example Report when an 'wearable' entity is deleted. + * Entities.deletingWearable.connect(function (entityID) { + * print("Deleted wearable: " + entityID); + * }); + */ + void deletingWearable(const EntityItemID& entityID); + + /**jsdoc + * Triggered when an 'wearable' entity is added to Interface's local in-memory tree of entities it knows about. This may occur when + * 'wearable' entities are added to avatar + * @function Entities.addingWearable + * @param {Uuid} entityID - The ID of the 'wearable' entity added. + * @returns {Signal} + * @example Report when an 'wearable' entity is added. + * Entities.addingWearable.connect(function (entityID) { + * print("Added wearable: " + entityID); + * }); + */ + void addingWearable(const EntityItemID& entityID); + /**jsdoc * Triggered when you disconnect from a domain, at which time Interface's local in-memory tree of entities it knows about * is cleared. @@ -1938,6 +1963,8 @@ protected: private slots: void handleEntityScriptCallMethodPacket(QSharedPointer receivedMessage, SharedNodePointer senderNode); + void onAddingEntity(EntityItem* entity); + void onDeletingEntity(EntityItem* entity); private: bool actionWorker(const QUuid& entityID, std::function actor); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index b23be66ade..dd020da5a0 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -305,6 +305,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { fixupNeedsParentFixups(); emit addingEntity(entity->getEntityItemID()); + emit addingEntityPointer(entity.get()); } bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index c6a590ec71..d7f93b1eb2 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -309,6 +309,7 @@ signals: void deletingEntity(const EntityItemID& entityID); void deletingEntityPointer(EntityItem* entityID); void addingEntity(const EntityItemID& entityID); + void addingEntityPointer(EntityItem* entityID); void editingEntityPointer(const EntityItemPointer& entityID); void entityScriptChanging(const EntityItemID& entityItemID, const bool reload); void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index b4b00e57a7..e68e287ee2 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -519,7 +519,6 @@ QVector ModelEntityItem::getJointTranslationsSet() const { return result; } - bool ModelEntityItem::hasModel() const { return resultWithReadLock([&] { return !_modelURL.isEmpty(); diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index acc49ce2a2..8ecfb84633 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -31,6 +31,8 @@ static const QUrl DEFAULT_SCRIPTS_LOCATION { "file:///~//defaultScripts.js" }; // Using a QVariantList so this is human-readable in the settings file static Setting::Handle runningScriptsHandle(SETTINGS_KEY, { QVariant(DEFAULT_SCRIPTS_LOCATION) }); +const int RELOAD_ALL_SCRIPTS_TIMEOUT = 1000; + ScriptsModel& getScriptsModel() { static ScriptsModel scriptsModel; @@ -387,15 +389,10 @@ void ScriptEngines::stopAllScripts(bool restart) { // queue user scripts if restarting if (restart && scriptEngine->isUserLoaded()) { _isReloading = true; - bool lastScript = (it == _scriptEnginesHash.constEnd() - 1); ScriptEngine::Type type = scriptEngine->getType(); - connect(scriptEngine.data(), &ScriptEngine::finished, this, [this, type, lastScript] (QString scriptName) { + connect(scriptEngine.data(), &ScriptEngine::finished, this, [this, type] (QString scriptName) { reloadScript(scriptName, true)->setType(type); - - if (lastScript) { - _isReloading = false; - } }); } @@ -405,6 +402,9 @@ void ScriptEngines::stopAllScripts(bool restart) { if (restart) { qCDebug(scriptengine) << "stopAllScripts -- emitting scriptsReloading"; + QTimer::singleShot(RELOAD_ALL_SCRIPTS_TIMEOUT, this, [&] { + _isReloading = false; + }); emit scriptsReloading(); } } diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 81a6447aae..65abf791a5 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -69,12 +69,12 @@ function getMyAvatarSettings() { } } -function updateAvatarWearables(avatar, bookmarkAvatarName, callback) { +function updateAvatarWearables(avatar, callback) { executeLater(function() { var wearables = getMyAvatarWearables(); avatar[ENTRY_AVATAR_ENTITIES] = wearables; - sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables, 'avatarName' : bookmarkAvatarName}) + sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}) if(callback) callback(); @@ -188,7 +188,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See sendToQml(message) break; case 'selectAvatar': + Entities.addingWearable.disconnect(onAddingWearable); + Entities.deletingWearable.disconnect(onDeletingWearable); AvatarBookmarks.loadBookmark(message.name); + Entities.addingWearable.connect(onAddingWearable); + Entities.deletingWearable.connect(onDeletingWearable); break; case 'deleteAvatar': AvatarBookmarks.removeBookmark(message.name); @@ -223,7 +227,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See // revert changes using snapshot of wearables if(currentAvatarWearablesBackup !== null) { AvatarBookmarks.updateAvatarEntities(currentAvatarWearablesBackup); - updateAvatarWearables(currentAvatar, message.avatarName); + updateAvatarWearables(currentAvatar); } } else { sendToQml({'method' : 'updateAvatarInBookmarks'}); @@ -256,8 +260,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See parentJointIndex: hipsIndex }; + Entities.addingWearable.disconnect(onAddingWearable); var entityID = Entities.addEntity(properties, true); - updateAvatarWearables(currentAvatar, message.avatarName, function() { + Entities.addingWearable.connect(onAddingWearable); + + updateAvatarWearables(currentAvatar, function() { onSelectedEntity(entityID); }); break; @@ -265,8 +272,12 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See ensureWearableSelected(message.entityID); break; case 'deleteWearable': + + Entities.deletingWearable.disconnect(onDeletingWearable); Entities.deleteEntity(message.entityID); - updateAvatarWearables(currentAvatar, message.avatarName); + Entities.deletingWearable.connect(onDeletingWearable); + + updateAvatarWearables(currentAvatar); break; case 'changeDisplayName': if (MyAvatar.displayName !== message.displayName) { @@ -380,6 +391,18 @@ function onSelectedEntity(entityID, pointerEvent) { } } +function onAddingWearable(entityID) { + updateAvatarWearables(currentAvatar, function() { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + }); +} + +function onDeletingWearable(entityID) { + updateAvatarWearables(currentAvatar, function() { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + }); +} + function handleWearableMessages(channel, message, sender) { if (channel !== 'Hifi-Object-Manipulation') { return; @@ -485,6 +508,8 @@ function off() { AvatarBookmarks.bookmarkDeleted.disconnect(onBookmarkDeleted); AvatarBookmarks.bookmarkAdded.disconnect(onBookmarkAdded); + Entities.addingWearable.disconnect(onAddingWearable); + Entities.deletingWearable.disconnect(onDeletingWearable); MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); @@ -495,16 +520,23 @@ function off() { } function on() { - AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded); - AvatarBookmarks.bookmarkDeleted.connect(onBookmarkDeleted); - AvatarBookmarks.bookmarkAdded.connect(onBookmarkAdded); - MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); - MyAvatar.dominantHandChanged.connect(onDominantHandChanged); - MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); - MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); - MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); - MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); + if (!isWired) { // It is not ok to connect these twice, hence guard. + isWired = true; + + AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded); + AvatarBookmarks.bookmarkDeleted.connect(onBookmarkDeleted); + AvatarBookmarks.bookmarkAdded.connect(onBookmarkAdded); + + Entities.addingWearable.connect(onAddingWearable); + Entities.deletingWearable.connect(onDeletingWearable); + MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); + MyAvatar.dominantHandChanged.connect(onDominantHandChanged); + MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); + MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); + MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); + MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); + } } function onTabletButtonClicked() { @@ -514,7 +546,6 @@ function onTabletButtonClicked() { } else { ContextOverlay.enabled = false; tablet.loadQMLSource(AVATARAPP_QML_SOURCE); - isWired = true; } } var hasEventBridge = false; diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 2658f11989..1cda6ff67b 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -424,9 +424,19 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } }; + this.leftBlacklistTabletIDs = []; + this.rightBlacklistTabletIDs = []; + + this.setLeftBlacklist = function () { + Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist.concat(_this.leftBlacklistTabletIDs)); + }; + this.setRightBlacklist = function () { + Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist.concat(_this.rightBlacklistTabletIDs)); + }; + this.setBlacklist = function() { - Pointers.setIgnoreItems(_this.leftPointer, this.blacklist); - Pointers.setIgnoreItems(_this.rightPointer, this.blacklist); + _this.setLeftBlacklist(); + _this.setRightBlacklist(); }; var MAPPING_NAME = "com.highfidelity.controllerDispatcher"; @@ -490,7 +500,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE, enabled: true }); - this.handleHandMessage = function(channel, data, sender) { + this.handleMessage = function (channel, data, sender) { var message; if (sender === MyAvatar.sessionUUID) { try { @@ -511,6 +521,18 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); _this.setBlacklist(); } } + + if (action === "tablet") { + var tabletIDs = message.blacklist + ? [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHighlightID] : []; + if (message.hand === LEFT_HAND) { + _this.leftBlacklistTabletIDs = tabletIDs; + _this.setLeftBlacklist(); + } else { + _this.rightBlacklistTabletIDs = tabletIDs; + _this.setRightBlacklist(); + } + } } } catch (e) { print("WARNING: handControllerGrab.js -- error parsing message: " + data); @@ -550,7 +572,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var controllerDispatcher = new ControllerDispatcher(); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); - Messages.messageReceived.connect(controllerDispatcher.handleHandMessage); + Messages.messageReceived.connect(controllerDispatcher.handleMessage); + Script.scriptEnding.connect(controllerDispatcher.cleanup); Script.setTimeout(controllerDispatcher.update, BASIC_TIMER_INTERVAL_MS); }()); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js index f983ed1e7d..0ba3dd6e6b 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js @@ -48,6 +48,7 @@ Script.include("/~/system/libraries/controllers.js"); function FarActionGrabEntity(hand) { this.hand = hand; + this.grabbing = false; this.grabbedThingID = null; this.targetObject = null; this.actionID = null; // action this script created... @@ -151,6 +152,7 @@ Script.include("/~/system/libraries/controllers.js"); Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); this.previousRoomControllerPosition = roomControllerPosition; + this.grabbing = true; }; this.continueDistanceHolding = function(controllerData) { @@ -246,6 +248,7 @@ Script.include("/~/system/libraries/controllers.js"); this.grabbedThingID = null; this.targetObject = null; this.potentialEntityWithContextOverlay = false; + this.grabbing = false; }; this.updateRecommendedArea = function() { @@ -357,8 +360,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function (controllerData) { - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || - this.notPointingAtEntity(controllerData) || this.targetIsNull()) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) { this.endFarGrabAction(); Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); @@ -375,10 +377,12 @@ Script.include("/~/system/libraries/controllers.js"); this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar", this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", - this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", - this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay", - this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight" + this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity" ]; + if (!this.grabbing) { + nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); + nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"); + } var nearGrabReadiness = []; for (var i = 0; i < nearGrabNames.length; i++) { diff --git a/scripts/system/controllers/controllerModules/farParentGrabEntity.js b/scripts/system/controllers/controllerModules/farParentGrabEntity.js index 230c4f06db..9960b08292 100644 --- a/scripts/system/controllers/controllerModules/farParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farParentGrabEntity.js @@ -47,6 +47,7 @@ Script.include("/~/system/libraries/controllers.js"); function FarParentGrabEntity(hand) { this.hand = hand; + this.grabbing = false; this.targetEntityID = null; this.targetObject = null; this.previouslyUnhooked = {}; @@ -455,8 +456,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function (controllerData) { - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || - this.notPointingAtEntity(controllerData) || this.targetIsNull()) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) { this.endFarParentGrab(controllerData); Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); this.highlightedEntity = null; @@ -472,10 +472,12 @@ Script.include("/~/system/libraries/controllers.js"); this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar", this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", - this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", - this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay", - this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight" + this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity" ]; + if (!this.grabbing) { + nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); + nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"); + } var nearGrabReadiness = []; for (var i = 0; i < nearGrabNames.length; i++) { @@ -485,11 +487,10 @@ Script.include("/~/system/libraries/controllers.js"); } if (this.targetEntityID) { - // if we are doing a distance grab and the object or tablet gets close enough to the controller, + // if we are doing a distance grab and the object gets close enough to the controller, // stop the far-grab so the near-grab or equip can take over. for (var k = 0; k < nearGrabReadiness.length; k++) { - if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.targetEntityID || - HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) { + if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.targetEntityID)) { this.endFarParentGrab(controllerData); return makeRunningValues(false, [], []); } diff --git a/scripts/system/controllers/controllerModules/farTrigger.js b/scripts/system/controllers/controllerModules/farTrigger.js index 2b003e4732..c9c9d3deee 100644 --- a/scripts/system/controllers/controllerModules/farTrigger.js +++ b/scripts/system/controllers/controllerModules/farTrigger.js @@ -37,7 +37,7 @@ Script.include("/~/system/libraries/controllers.js"); this.getTargetProps = function (controllerData) { var targetEntity = controllerData.rayPicks[this.hand].objectID; - if (targetEntity) { + if (targetEntity && controllerData.rayPicks[this.hand].type === RayPick.INTERSECTED_ENTITY) { var targetProperties = Entities.getEntityProperties(targetEntity, DISPATCHER_PROPERTIES); if (entityWantsFarTrigger(targetProperties)) { return targetProperties; diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index 6adfa88fb2..5709b19efe 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -20,6 +20,7 @@ Script.include("/~/system/libraries/utils.js"); var MARGIN = 25; function InEditMode(hand) { this.hand = hand; + this.isEditing = false; this.triggerClicked = false; this.selectedTarget = null; this.reticleMinX = MARGIN; @@ -62,25 +63,27 @@ Script.include("/~/system/libraries/utils.js"); return point2d; }; + this.ENTITY_TOOL_UPDATES_CHANNEL = "entityToolUpdates"; + this.sendPickData = function(controllerData) { if (controllerData.triggerClicks[this.hand]) { var hand = this.hand === RIGHT_HAND ? Controller.Standard.RightHand : Controller.Standard.LeftHand; if (!this.triggerClicked) { this.selectedTarget = controllerData.rayPicks[this.hand]; if (!this.selectedTarget.intersects) { - Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ + Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({ method: "clearSelection", hand: hand })); } else { if (this.selectedTarget.type === Picks.INTERSECTED_ENTITY) { - Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ + Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({ method: "selectEntity", entityID: this.selectedTarget.objectID, hand: hand })); } else if (this.selectedTarget.type === Picks.INTERSECTED_OVERLAY) { - Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ + Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({ method: "selectOverlay", overlayID: this.selectedTarget.objectID, hand: hand @@ -102,7 +105,7 @@ Script.include("/~/system/libraries/utils.js"); var desktopWindow = Window.isPointOnDesktopWindow(point2d); var tablet = this.pointingAtTablet(rayPick.objectID); var rightHand = this.hand === RIGHT_HAND; - Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ + Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({ method: "pointingAt", desktopWindow: desktopWindow, tablet: tablet, @@ -110,6 +113,10 @@ Script.include("/~/system/libraries/utils.js"); })); }; + this.runModule = function() { + return makeRunningValues(true, [], []); + }; + this.exitModule = function() { return makeRunningValues(false, [], []); }; @@ -120,13 +127,15 @@ Script.include("/~/system/libraries/utils.js"); this.triggerClicked = false; } Messages.sendLocalMessage('Hifi-unhighlight-all', ''); - return makeRunningValues(true, [], []); + return this.runModule(); } this.triggerClicked = false; - return makeRunningValues(false, [], []); + return this.exitModule(); }; this.run = function(controllerData) { + + // Tablet stylus. var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightTabletStylusInput" : "LeftTabletStylusInput"); if (tabletStylusInput) { @@ -136,6 +145,7 @@ Script.include("/~/system/libraries/utils.js"); } } + // Tablet surface. var webLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput"); if (webLaser) { @@ -147,6 +157,7 @@ Script.include("/~/system/libraries/utils.js"); } } + // HUD overlay. if (!controllerData.triggerClicks[this.hand]) { // Don't grab if trigger pressed when laser starts intersecting. var hudLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightHudOverlayPointer" : "LeftHudOverlayPointer"); @@ -168,6 +179,7 @@ Script.include("/~/system/libraries/utils.js"); } } + // Teleport. var teleport = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightTeleporter" : "LeftTeleporter"); if (teleport) { var teleportReady = teleport.isReady(controllerData); @@ -176,8 +188,6 @@ Script.include("/~/system/libraries/utils.js"); } } - var stopRunning = false; - if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0)) { var stopRunning = false; controllerData.nearbyOverlayIDs[this.hand].forEach(function(overlayID) { @@ -203,6 +213,37 @@ Script.include("/~/system/libraries/utils.js"); enableDispatcherModule("LeftHandInEditMode", leftHandInEditMode); enableDispatcherModule("RightHandInEditMode", rightHandInEditMode); + var INEDIT_STATUS_CHANNEL = "Hifi-InEdit-Status"; + var HAND_RAYPICK_BLACKLIST_CHANNEL = "Hifi-Hand-RayPick-Blacklist"; + this.handleMessage = function (channel, data, sender) { + if (channel === INEDIT_STATUS_CHANNEL && sender === MyAvatar.sessionUUID) { + var message; + + try { + message = JSON.parse(data); + } catch (e) { + return; + } + + switch (message.method) { + case "editing": + if (message.hand === LEFT_HAND) { + leftHandInEditMode.isEditing = message.editing; + } else { + rightHandInEditMode.isEditing = message.editing; + } + Messages.sendLocalMessage(HAND_RAYPICK_BLACKLIST_CHANNEL, JSON.stringify({ + action: "tablet", + hand: message.hand, + blacklist: message.editing + })); + break; + } + } + }; + Messages.subscribe(INEDIT_STATUS_CHANNEL); + Messages.messageReceived.connect(this.handleMessage); + function cleanup() { disableDispatcherModule("LeftHandInEditMode"); disableDispatcherModule("RightHandInEditMode"); diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index 0c04918ab1..104e37d76c 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -18,7 +18,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); function InVREditMode(hand) { this.hand = hand; - this.disableModules = false; + this.isAppActive = false; + this.isEditing = false; this.running = false; var NO_HAND_LASER = -1; // Invalid hand parameter so that standard laser is not displayed. this.parameters = makeDispatcherModuleParameters( @@ -66,7 +67,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); }; this.isReady = function (controllerData) { - if (this.disableModules) { + if (this.isAppActive) { return makeRunningValues(true, [], []); } return makeRunningValues(false, [], []); @@ -74,14 +75,13 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.run = function (controllerData) { // Default behavior if disabling is not enabled. - if (!this.disableModules) { + if (!this.isAppActive) { return this.exitModule(); } // Tablet stylus. - var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND ? - "RightTabletStylusInput" : - "LeftTabletStylusInput"); + var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightTabletStylusInput" : "LeftTabletStylusInput"); if (tabletStylusInput) { var tabletReady = tabletStylusInput.isReady(controllerData); if (tabletReady.active) { @@ -90,9 +90,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } // Tablet surface. - var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ? - "RightWebSurfaceLaserInput" : - "LeftWebSurfaceLaserInput"); + var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput"); if (overlayLaser) { var overlayLaserReady = overlayLaser.isReady(controllerData); var target = controllerData.rayPicks[this.hand].objectID; @@ -114,8 +113,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // HUD overlay. if (!controllerData.triggerClicks[this.hand]) { var hudLaser = getEnabledModuleByName(this.hand === RIGHT_HAND - ? "RightHudOverlayPointer" - : "LeftHudOverlayPointer"); + ? "RightHudOverlayPointer" : "LeftHudOverlayPointer"); if (hudLaser) { var hudLaserReady = hudLaser.isReady(controllerData); if (hudLaserReady.active) { @@ -125,9 +123,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } // Teleport. - var teleporter = getEnabledModuleByName(this.hand === RIGHT_HAND ? - "RightTeleporter" : - "LeftTeleporter"); + var teleporter = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightTeleporter" : "LeftTeleporter"); if (teleporter) { var teleporterReady = teleporter.isReady(controllerData); if (teleporterReady.active) { @@ -145,19 +142,39 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); enableDispatcherModule("LeftHandInVREditMode", leftHandInVREditMode); enableDispatcherModule("RightHandInVREditMode", rightHandInVREditMode); - var INVREDIT_DISABLER_MESSAGE_CHANNEL = "Hifi-InVREdit-Disabler"; - this.handleMessage = function (channel, message, sender) { - if (sender === MyAvatar.sessionUUID && channel === INVREDIT_DISABLER_MESSAGE_CHANNEL) { - if (message === "both") { - leftHandInVREditMode.disableModules = true; - rightHandInVREditMode.disableModules = true; - } else if (message === "none") { - leftHandInVREditMode.disableModules = false; - rightHandInVREditMode.disableModules = false; + var INVREDIT_STATUS_CHANNEL = "Hifi-InVREdit-Status"; + var HAND_RAYPICK_BLACKLIST_CHANNEL = "Hifi-Hand-RayPick-Blacklist"; + this.handleMessage = function (channel, data, sender) { + if (channel === INVREDIT_STATUS_CHANNEL && sender === MyAvatar.sessionUUID) { + var message; + + try { + message = JSON.parse(data); + } catch (e) { + return; + } + + switch (message.method) { + case "active": + leftHandInVREditMode.isAppActive = message.active; + rightHandInVREditMode.isAppActive = message.active; + break; + case "editing": + if (message.hand === LEFT_HAND) { + leftHandInVREditMode.isEditing = message.editing; + } else { + rightHandInVREditMode.isEditing = message.editing; + } + Messages.sendLocalMessage(HAND_RAYPICK_BLACKLIST_CHANNEL, JSON.stringify({ + action: "tablet", + hand: message.hand, + blacklist: message.editing + })); + break; } } }; - Messages.subscribe(INVREDIT_DISABLER_MESSAGE_CHANNEL); + Messages.subscribe(INVREDIT_STATUS_CHANNEL); Messages.messageReceived.connect(this.handleMessage); this.cleanup = function () { diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index 9bddeb236a..e59b2e35ad 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -9,7 +9,7 @@ /* global Script, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex, enableDispatcherModule, disableDispatcherModule, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, makeDispatcherModuleParameters, Overlays, makeRunningValues, Vec3, resizeTablet, getTabletWidthFromSettings, - NEAR_GRAB_RADIUS, HMD, Uuid + NEAR_GRAB_RADIUS, HMD, Uuid, getEnabledModuleByName */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -176,10 +176,23 @@ Script.include("/~/system/libraries/utils.js"); return null; }; + this.isEditing = function () { + var inEditModeModule = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightHandInEditMode" : "LeftHandInEditMode"); + if (inEditModeModule && inEditModeModule.isEditing) { + return true; + } + var inVREditModeModule = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightHandInVREditMode" : "LeftHandInVREditMode"); + if (inVREditModeModule && inVREditModeModule.isEditing) { + return true; + } + return false; + }; this.isReady = function (controllerData) { - if ((controllerData.triggerClicks[this.hand] === 0 && - controllerData.secondaryValues[this.hand] === 0)) { + if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) + || this.isEditing()) { this.robbed = false; return makeRunningValues(false, [], []); } @@ -202,7 +215,8 @@ Script.include("/~/system/libraries/utils.js"); }; this.run = function (controllerData) { - if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) || !this.isGrabbedThingVisible()) { + if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) + || this.isEditing() || !this.isGrabbedThingVisible()) { this.endNearParentingGrabOverlay(); this.robbed = false; return makeRunningValues(false, [], []); diff --git a/scripts/system/controllers/controllerModules/nearTabletHighlight.js b/scripts/system/controllers/controllerModules/nearTabletHighlight.js index c24464ab38..2e046f5dc6 100644 --- a/scripts/system/controllers/controllerModules/nearTabletHighlight.js +++ b/scripts/system/controllers/controllerModules/nearTabletHighlight.js @@ -11,7 +11,7 @@ // /* global LEFT_HAND, RIGHT_HAND, makeDispatcherModuleParameters, makeRunningValues, enableDispatcherModule, - * disableDispatcherModule */ + * disableDispatcherModule, getEnabledModuleByName */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -66,12 +66,26 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); 100 ); + this.isEditing = function () { + var inEditModeModule = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightHandInEditMode" : "LeftHandInEditMode"); + if (inEditModeModule && inEditModeModule.isEditing) { + return true; + } + var inVREditModeModule = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightHandInVREditMode" : "LeftHandInVREditMode"); + if (inVREditModeModule && inVREditModeModule.isEditing) { + return true; + } + return false; + }; + this.isNearTablet = function (controllerData) { return HMD.tabletID && controllerData.nearbyOverlayIDs[this.hand].indexOf(HMD.tabletID) !== -1; }; this.isReady = function (controllerData) { - if (this.isNearTablet(controllerData)) { + if (!this.isEditing() && this.isNearTablet(controllerData)) { return makeRunningValues(true, [], []); } setTabletNearGrabbable(this.hand, false); @@ -79,7 +93,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); }; this.run = function (controllerData) { - if (!this.isNearTablet(controllerData)) { + if (this.isEditing() || !this.isNearTablet(controllerData)) { setTabletNearGrabbable(this.hand, false); return makeRunningValues(false, [], []); } diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 1f0bc9404c..164899ef86 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -639,6 +639,8 @@ SelectionDisplay = (function() { ROLL: 2 }; + const INEDIT_STATUS_CHANNEL = "Hifi-InEdit-Status"; + /** * The current space mode, this could have been a forced space mode since we do not support multi selection while in * local space mode. @@ -985,6 +987,7 @@ SelectionDisplay = (function() { that.triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); that.triggeredHand = NO_HAND; that.pressedHand = NO_HAND; + that.editingHand = NO_HAND; that.triggered = function() { return that.triggeredHand !== NO_HAND; }; @@ -1117,6 +1120,12 @@ SelectionDisplay = (function() { activeTool = hitTool; that.clearDebugPickPlane(); if (activeTool.onBegin) { + that.editingHand = that.triggeredHand; + Messages.sendLocalMessage(INEDIT_STATUS_CHANNEL, JSON.stringify({ + method: "editing", + hand: that.editingHand === Controller.Standard.LeftHand ? LEFT_HAND : RIGHT_HAND, + editing: true + })); activeTool.onBegin(event, pickRay, results); } else { print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool(" + activeTool.mode + ") missing onBegin"); @@ -1265,6 +1274,12 @@ SelectionDisplay = (function() { if (wantDebug) { print(" Triggering ActiveTool(" + activeTool.mode + ")'s onEnd"); } + Messages.sendLocalMessage(INEDIT_STATUS_CHANNEL, JSON.stringify({ + method: "editing", + hand: that.editingHand === Controller.Standard.LeftHand ? LEFT_HAND : RIGHT_HAND, + editing: false + })); + that.editingHand = NO_HAND; activeTool.onEnd(event); } else if (wantDebug) { print(" ActiveTool(" + activeTool.mode + ")'s missing onEnd"); diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index cc201f58f7..780dacf85e 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -251,11 +251,8 @@ } - function getUIPositionAndRotation(hand) { - return { - position: MINI_POSITIONS[hand], - rotation: MINI_ROTATIONS[hand] - }; + function getUIPosition(hand) { + return MINI_POSITIONS[hand]; } function getMiniTabletID() { @@ -493,7 +490,7 @@ create(); return { - getUIPositionAndRotation: getUIPositionAndRotation, + getUIPosition: getUIPosition, getMiniTabletID: getMiniTabletID, getMiniTabletProperties: getMiniTabletProperties, isLaserPointingAt: isLaserPointingAt, @@ -552,14 +549,23 @@ // Trigger values. leftTriggerOn = 0, rightTriggerOn = 0, - MAX_TRIGGER_ON_TIME = 100, + MAX_TRIGGER_ON_TIME = 400, // Visibility. - MAX_HAND_CAMERA_ANGLE = 30, - MAX_CAMERA_HAND_ANGLE = 30, + MAX_MEDIAL_FINGER_CAMERA_ANGLE = 25, // From palm normal along palm towards fingers. + MAX_MEDIAL_WRIST_CAMERA_ANGLE = 65, // From palm normal along palm towards wrist. + MAX_LATERAL_THUMB_CAMERA_ANGLE = 25, // From palm normal across palm towards of thumb. + MAX_LATERAL_PINKY_CAMERA_ANGLE = 25, // From palm normal across palm towards pinky. DEGREES_180 = 180, - MAX_HAND_CAMERA_ANGLE_COS = Math.cos(Math.PI * MAX_HAND_CAMERA_ANGLE / DEGREES_180), - MAX_CAMERA_HAND_ANGLE_COS = Math.cos(Math.PI * MAX_CAMERA_HAND_ANGLE / DEGREES_180), + DEGREES_TO_RADIANS = Math.PI / DEGREES_180, + MAX_MEDIAL_FINGER_CAMERA_ANGLE_RAD = DEGREES_TO_RADIANS * MAX_MEDIAL_FINGER_CAMERA_ANGLE, + MAX_MEDIAL_WRIST_CAMERA_ANGLE_RAD = DEGREES_TO_RADIANS * MAX_MEDIAL_WRIST_CAMERA_ANGLE, + MAX_LATERAL_THUMB_CAMERA_ANGLE_RAD = DEGREES_TO_RADIANS * MAX_LATERAL_THUMB_CAMERA_ANGLE, + MAX_LATERAL_PINKY_CAMERA_ANGLE_RAD = DEGREES_TO_RADIANS * MAX_LATERAL_PINKY_CAMERA_ANGLE, + MAX_CAMERA_MINI_ANGLE = 30, + MAX_CAMERA_MINI_ANGLE_COS = Math.cos(MAX_CAMERA_MINI_ANGLE * DEGREES_TO_RADIANS), + SHOWING_DELAY = 1000, // ms + lastInvisible = [0, 0], HIDING_DELAY = 1000, // ms lastVisible = [0, 0]; @@ -598,11 +604,18 @@ jointIndex, handPosition, handOrientation, - uiPositionAndOrientation, miniPosition, - miniOrientation, miniToCameraDirection, - cameraToHand; + normalHandVector, + medialHandVector, + lateralHandVector, + normalDot, + medialDot, + lateralDot, + medialAngle, + lateralAngle, + cameraToMini, + now; // Shouldn't show mini tablet if hand isn't being controlled. pose = Controller.getPoseValue(hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand); @@ -647,27 +660,48 @@ Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex))); handOrientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex)); - uiPositionAndOrientation = ui.getUIPositionAndRotation(hand); + var uiPosition = ui.getUIPosition(hand); miniPosition = Vec3.sum(handPosition, Vec3.multiply(MyAvatar.sensorToWorldScale, - Vec3.multiplyQbyV(handOrientation, uiPositionAndOrientation.position))); - miniOrientation = Quat.multiply(handOrientation, uiPositionAndOrientation.rotation); + Vec3.multiplyQbyV(handOrientation, uiPosition))); miniToCameraDirection = Vec3.normalize(Vec3.subtract(Camera.position, miniPosition)); - show = Vec3.dot(miniToCameraDirection, Quat.getForward(miniOrientation)) > MAX_HAND_CAMERA_ANGLE_COS; - show = show || (-Vec3.dot(miniToCameraDirection, Quat.getForward(handOrientation)) > MAX_HAND_CAMERA_ANGLE_COS); - cameraToHand = -Vec3.dot(miniToCameraDirection, Quat.getForward(Camera.orientation)); - show = show && (cameraToHand > MAX_CAMERA_HAND_ANGLE_COS); + + // Mini tablet aimed toward camera? + medialHandVector = Vec3.multiplyQbyV(handOrientation, Vec3.UNIT_Y); + lateralHandVector = Vec3.multiplyQbyV(handOrientation, hand === LEFT_HAND ? Vec3.UNIT_X : Vec3.UNIT_NEG_X); + normalHandVector = Vec3.multiplyQbyV(handOrientation, Vec3.UNIT_Z); + medialDot = Vec3.dot(medialHandVector, miniToCameraDirection); + lateralDot = Vec3.dot(lateralHandVector, miniToCameraDirection); + normalDot = Vec3.dot(normalHandVector, miniToCameraDirection); + medialAngle = Math.atan2(medialDot, normalDot); + lateralAngle = Math.atan2(lateralDot, normalDot); + show = -MAX_MEDIAL_WRIST_CAMERA_ANGLE_RAD <= medialAngle + && medialAngle <= MAX_MEDIAL_FINGER_CAMERA_ANGLE_RAD + && -MAX_LATERAL_THUMB_CAMERA_ANGLE_RAD <= lateralAngle + && lateralAngle <= MAX_LATERAL_PINKY_CAMERA_ANGLE_RAD; + + // Camera looking at mini tablet? + cameraToMini = -Vec3.dot(miniToCameraDirection, Quat.getForward(Camera.orientation)); + show = show && (cameraToMini > MAX_CAMERA_MINI_ANGLE_COS); + + // Delay showing for a while after it would otherwise be shown, unless it was showing on the other hand. + now = Date.now(); + if (show) { + show = now - lastInvisible[hand] >= SHOWING_DELAY || now - lastVisible[otherHand(hand)] <= HIDING_DELAY; + } else { + lastInvisible[hand] = now; + } // Persist showing for a while after it would otherwise be hidden. if (show) { - lastVisible[hand] = Date.now(); + lastVisible[hand] = now; } else { - show = Date.now() - lastVisible[hand] <= HIDING_DELAY; + show = now - lastVisible[hand] <= HIDING_DELAY; } } return { show: show, - cameraToHand: cameraToHand + cameraToMini: cameraToMini }; } @@ -690,7 +724,7 @@ showRight = shouldShowMini(RIGHT_HAND); if (showLeft.show && showRight.show) { // Both hands would be pointing at camera; show the one the camera is gazing at. - if (showLeft.cameraToHand > showRight.cameraToHand) { + if (showLeft.cameraToMini > showRight.cameraToMini) { setState(MINI_SHOWING, LEFT_HAND); } else { setState(MINI_SHOWING, RIGHT_HAND); @@ -752,7 +786,7 @@ showLeft = shouldShowMini(LEFT_HAND); showRight = shouldShowMini(RIGHT_HAND); if (showLeft.show && showRight.show) { - if (showLeft.cameraToHand > showRight.cameraToHand) { + if (showLeft.cameraToMini > showRight.cameraToMini) { if (miniHand !== LEFT_HAND) { setState(MINI_HIDING); }