From 813c58a1fb23d90a35d82e680ac0dce558510cd9 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 17 Jul 2018 11:53:55 -0700 Subject: [PATCH 001/744] adding check to see if near grabbing is ready --- .../controllerModules/webSurfaceLaserInput.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index a2fe0bfcd4..1706fadbb7 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -42,6 +42,16 @@ Script.include("/~/system/libraries/controllers.js"); return true; } } + nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity"; + nearGrabModule = getEnabledModuleByName(nearGrabName); + if (nearGrabModule) { + return nearGrabModule.isReady(controllerData).active; + } + nearGrabName = this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity"; + nearGrabModule = getEnabledModuleByName(nearGrabName); + if (nearGrabModule) { + return nearGrabModule.isReady(controllerData).active; + } } return false; }; From eb7bde1bfca86eecf2ef8d291313f9dfe61c3603 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 17 Jul 2018 15:38:56 -0700 Subject: [PATCH 002/744] switching near grab priority to be before web surface laser --- .../highlightNearbyEntities.js | 2 +- .../controllerModules/inEditMode.js | 2 +- .../controllerModules/inVREditMode.js | 2 +- .../controllerModules/nearActionGrabEntity.js | 2 +- .../nearGrabHyperLinkEntity.js | 2 +- .../controllerModules/nearParentGrabEntity.js | 2 +- .../controllerModules/nearTrigger.js | 2 +- .../controllerModules/webSurfaceLaserInput.js | 32 +++++++++++++++++-- 8 files changed, 36 insertions(+), 10 deletions(-) diff --git a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js index bc09ebee7a..3a33082f64 100644 --- a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js +++ b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js @@ -37,7 +37,7 @@ this.highlightedEntities = []; this.parameters = dispatcherUtils.makeDispatcherModuleParameters( - 480, + 120, this.hand === dispatcherUtils.RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index a724c2037b..0c24e64af7 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -30,7 +30,7 @@ Script.include("/~/system/libraries/utils.js"); this.reticleMaxY; this.parameters = makeDispatcherModuleParameters( - 160, + 200, this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], [], 100, diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index 7b78d5e1c4..02863cf935 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -21,7 +21,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.disableModules = false; var NO_HAND_LASER = -1; // Invalid hand parameter so that default laser is not displayed. this.parameters = makeDispatcherModuleParameters( - 200, // Not too high otherwise the tablet laser doesn't work. + 240, // Not too high otherwise the tablet laser doesn't work. this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index f528c6f80f..f3574c668f 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -26,7 +26,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.hapticTargetID = null; this.parameters = makeDispatcherModuleParameters( - 500, + 140, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js index 962ae89bb9..366fcd3032 100644 --- a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js +++ b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js @@ -21,7 +21,7 @@ this.hyperlink = ""; this.parameters = makeDispatcherModuleParameters( - 485, + 125, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 38334f5523..ea55ee33f9 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -39,7 +39,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.cloneAllowed = true; this.parameters = makeDispatcherModuleParameters( - 500, + 140, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index 6a9cd9fbcd..f1126dedc3 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -29,7 +29,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.startSent = false; this.parameters = makeDispatcherModuleParameters( - 480, + 120, this.hand === RIGHT_HAND ? ["rightHandTrigger", "rightHand"] : ["leftHandTrigger", "leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 1706fadbb7..527ba0bd2f 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -22,7 +22,7 @@ Script.include("/~/system/libraries/controllers.js"); this.running = false; this.parameters = makeDispatcherModuleParameters( - 120, + 160, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100, @@ -60,6 +60,30 @@ Script.include("/~/system/libraries/controllers.js"); return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput; }; + this.isPointingAtGrabbableEntity = function(controllerData, triggerPressed) { + // we are searching for an entity that is not a web entity. We want to be able to + // grab a non-web entity if the ray-pick intersects one. + var intersection = controllerData.rayPicks[this.hand]; + if(intersection.type === Picks.INTERSECTED_ENTITY) { + // is pointing at an entity. + var entityProperty = Entities.getEntityProperties(intersection.objectID); + var entityType = entityProperty.type; + var isGrabbable = entityIsGrabbable(entityProperty); + return (isGrabbable && triggerPressed && entityType !== "Web"); + } else if (intersection.type === Picks.INTERSECTED_OVERLAY) { + var objectID = intersection.objectID; + if ((HMD.tabletID && objectID === HMD.tabletID) || + (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || + (HMD.homeButtonID && objectID === HMD.homeButtonID)) { + return true; + } else { + var overlayType = Overlays.getOverlayType(objectID); + return overlayType === "web3d" || triggerPressed; + } + } + return false; + } + this.isPointingAtTriggerable = function(controllerData, triggerPressed) { // allow pointing at tablet, unlocked web entities, or web overlays automatically without pressing trigger, // but for pointing at locked web entities or non-web overlays user must be pressing trigger @@ -113,7 +137,8 @@ Script.include("/~/system/libraries/controllers.js"); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; var allowThisModule = !otherModuleRunning || isTriggerPressed; - if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) { + if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) { + //this.isPointingAtGrabbableEntity(controllerData, isTriggerPressed)) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. @@ -134,7 +159,8 @@ Script.include("/~/system/libraries/controllers.js"); var allowThisModule = !otherModuleRunning && !grabModuleNeedsToRun; var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; - if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed))) { + if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) && + this.isPointingAtGrabbableEntity(controllerData, isTriggerPressed)) { this.running = true; return makeRunningValues(true, [], []); } From 30ca5a8cbd08a7fb440ea4baaf336c74c7a3e770 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 17 Jul 2018 16:44:49 -0700 Subject: [PATCH 003/744] code cleanup to use web laser if laser is not pointing at grabbable entity --- .../controllerModules/webSurfaceLaserInput.js | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 527ba0bd2f..9e07e125e7 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -64,25 +64,15 @@ Script.include("/~/system/libraries/controllers.js"); // we are searching for an entity that is not a web entity. We want to be able to // grab a non-web entity if the ray-pick intersects one. var intersection = controllerData.rayPicks[this.hand]; - if(intersection.type === Picks.INTERSECTED_ENTITY) { + if (intersection.type === Picks.INTERSECTED_ENTITY) { // is pointing at an entity. var entityProperty = Entities.getEntityProperties(intersection.objectID); var entityType = entityProperty.type; var isGrabbable = entityIsGrabbable(entityProperty); return (isGrabbable && triggerPressed && entityType !== "Web"); - } else if (intersection.type === Picks.INTERSECTED_OVERLAY) { - var objectID = intersection.objectID; - if ((HMD.tabletID && objectID === HMD.tabletID) || - (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || - (HMD.homeButtonID && objectID === HMD.homeButtonID)) { - return true; - } else { - var overlayType = Overlays.getOverlayType(objectID); - return overlayType === "web3d" || triggerPressed; - } } return false; - } + }; this.isPointingAtTriggerable = function(controllerData, triggerPressed) { // allow pointing at tablet, unlocked web entities, or web overlays automatically without pressing trigger, @@ -137,8 +127,8 @@ Script.include("/~/system/libraries/controllers.js"); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; var allowThisModule = !otherModuleRunning || isTriggerPressed; - if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) { - //this.isPointingAtGrabbableEntity(controllerData, isTriggerPressed)) { + if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed) && + !this.isPointingAtGrabbableEntity(controllerData, isTriggerPressed)) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. @@ -159,8 +149,8 @@ Script.include("/~/system/libraries/controllers.js"); var allowThisModule = !otherModuleRunning && !grabModuleNeedsToRun; var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; - if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) && - this.isPointingAtGrabbableEntity(controllerData, isTriggerPressed)) { + if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) && + !this.isPointingAtGrabbableEntity(controllerData, isTriggerPressed)) { this.running = true; return makeRunningValues(true, [], []); } From ac642f126a02ebb66303fee75f8dc3bf7ff06922 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 17 Jul 2018 20:52:50 -0700 Subject: [PATCH 004/744] adding extra conditional to ensure all modules are checked --- .../controllers/controllerModules/webSurfaceLaserInput.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 9e07e125e7..03c8a65637 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -44,12 +44,12 @@ Script.include("/~/system/libraries/controllers.js"); } nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity"; nearGrabModule = getEnabledModuleByName(nearGrabName); - if (nearGrabModule) { + if (nearGrabModule && nearGrabModule.isReady(controllerData)) { return nearGrabModule.isReady(controllerData).active; } nearGrabName = this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity"; nearGrabModule = getEnabledModuleByName(nearGrabName); - if (nearGrabModule) { + if (nearGrabModule && nearGrabModule.isReady(controllerData)) { return nearGrabModule.isReady(controllerData).active; } } From 5276d84aa5ccbb75f19f640cf60af794dba7d516 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 19 Jul 2018 17:08:12 -0700 Subject: [PATCH 005/744] pushing what i coded out loud - it broke the way grabbing works --- .../controllers/controllerDispatcher.js | 1 + .../controllerModules/webSurfaceLaserInput.js | 56 +++++++++++++------ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 4002fd297b..09d09248fc 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -298,6 +298,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue], triggerClicks: [_this.leftTriggerClicked, _this.rightTriggerClicked], secondaryValues: [_this.leftSecondaryValue, _this.rightSecondaryValue], + pointers: [_this.leftPointer, _this.rightPointer ], controllerLocations: controllerLocations, nearbyEntityProperties: nearbyEntityProperties, nearbyEntityPropertiesByID: nearbyEntityPropertiesByID, diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 03c8a65637..3ee50d0f75 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -5,11 +5,12 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Script, Entities, Controller, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, - makeRunningValues, Messages, Quat, Vec3, makeDispatcherModuleParameters, Overlays, ZERO_VEC, HMD, - INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, - COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, - TRIGGER_OFF_VALUE, getEnabledModuleByName, PICK_MAX_DISTANCE, ContextOverlay, Picks, makeLaserParams +/* global Script, Entities, Controller, RIGHT_HAND, LEFT_HAND, getControllerWorldLocation, + enableDispatcherModule, disableDispatcherModule, makeRunningValues, Messages, Quat, Vec3, + makeDispatcherModuleParameters, Overlays, ZERO_VEC, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, + getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, TRIGGER_OFF_VALUE, + getEnabledModuleByName, PICK_MAX_DISTANCE, ContextOverlay, Picks, makeLaserParams */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -19,6 +20,8 @@ Script.include("/~/system/libraries/controllers.js"); function WebSurfaceLaserInput(hand) { this.hand = hand; this.otherHand = this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND; + this.ignoredEntities = []; + this.lastObjectID = null; this.running = false; this.parameters = makeDispatcherModuleParameters( @@ -60,16 +63,20 @@ Script.include("/~/system/libraries/controllers.js"); return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput; }; - this.isPointingAtGrabbableEntity = function(controllerData, triggerPressed) { + this.isPointingAtNearGrabbableEntity = function(controllerData, triggerPressed) { // we are searching for an entity that is not a web entity. We want to be able to // grab a non-web entity if the ray-pick intersects one. var intersection = controllerData.rayPicks[this.hand]; if (intersection.type === Picks.INTERSECTED_ENTITY) { // is pointing at an entity. - var entityProperty = Entities.getEntityProperties(intersection.objectID); - var entityType = entityProperty.type; - var isGrabbable = entityIsGrabbable(entityProperty); - return (isGrabbable && triggerPressed && entityType !== "Web"); + var entityProperties = Entities.getEntityProperties(intersection.objectID); + var entityType = entityProperties.type; + if (entityIsGrabbable(entityProperties)) { + // check if entity is near grabbable. + var distance = Vec3.distance(entityProperties.position, controllerData.controllerLocations[this.hand].position); + if (distance <= NEAR_GRAB_RADIUS * MyAvatar.sensorToWorldScale) + return (triggerPressed && entityType !== "Web"); + } } return false; }; @@ -78,21 +85,30 @@ Script.include("/~/system/libraries/controllers.js"); // allow pointing at tablet, unlocked web entities, or web overlays automatically without pressing trigger, // but for pointing at locked web entities or non-web overlays user must be pressing trigger var intersection = controllerData.rayPicks[this.hand]; + var objectID = intersection.objectID; if (intersection.type === Picks.INTERSECTED_OVERLAY) { - var objectID = intersection.objectID; if ((HMD.tabletID && objectID === HMD.tabletID) || (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || (HMD.homeButtonID && objectID === HMD.homeButtonID)) { + this.lastObjectID = objectID; return true; } else { var overlayType = Overlays.getOverlayType(objectID); + this.lastObjectID = objectID; return overlayType === "web3d" || triggerPressed; } } else if (intersection.type === Picks.INTERSECTED_ENTITY) { - var entityProperty = Entities.getEntityProperties(intersection.objectID); - var entityType = entityProperty.type; - var isLocked = entityProperty.locked; - return entityType === "Web" && (!isLocked || triggerPressed); + var entityProperty = Entities.getEntityProperties(objectID); + if (entityProperty.type === "Web") { + var isLocked = entityProperty.locked; + this.lastObjectID = objectID; + return (!isLocked || triggerPressed); + } else if (this.ignoredEntities.indexOf(objectID) === -1 && triggerPressed && this.lastObjectID !== objectID) { + // ignore, preserve whether it's running or not. + console.log("I'm here"); + Pointers.setIgnoreItems(controllerData.pointers[this.hand], [objectID]); + this.ignoredEntities.push(objectID); + } } return false; }; @@ -110,6 +126,11 @@ Script.include("/~/system/libraries/controllers.js"); } }; + this.reAddIgnoredEntities = function(controllerData) { + Pointers.setIncludeItems(controllerData.pointers[this.hand], this.ignoredEntities); + this.ignoredEntities = []; + } + this.updateAllwaysOn = function() { var PREFER_STYLUS_OVER_LASER = "preferStylusOverLaser"; this.parameters.handLaser.allwaysOn = !Settings.getValue(PREFER_STYLUS_OVER_LASER, false); @@ -128,7 +149,7 @@ Script.include("/~/system/libraries/controllers.js"); controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; var allowThisModule = !otherModuleRunning || isTriggerPressed; if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed) && - !this.isPointingAtGrabbableEntity(controllerData, isTriggerPressed)) { + !this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. @@ -150,11 +171,12 @@ Script.include("/~/system/libraries/controllers.js"); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) && - !this.isPointingAtGrabbableEntity(controllerData, isTriggerPressed)) { + !this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)) { this.running = true; return makeRunningValues(true, [], []); } this.deleteContextOverlay(); + this.reAddIgnoredEntities(controllerData); this.running = false; this.dominantHandOverride = false; return makeRunningValues(false, [], []); From 4e3a6cb320b8ea8507512c8d5a7ab54a52cf737f Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 20 Jul 2018 11:18:20 -0700 Subject: [PATCH 006/744] adding ignore for far-/near- grabbable entities when operating on web surface --- .../controllerModules/webSurfaceLaserInput.js | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 3ee50d0f75..16e2c1a4f7 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -20,8 +20,6 @@ Script.include("/~/system/libraries/controllers.js"); function WebSurfaceLaserInput(hand) { this.hand = hand; this.otherHand = this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND; - this.ignoredEntities = []; - this.lastObjectID = null; this.running = false; this.parameters = makeDispatcherModuleParameters( @@ -71,11 +69,15 @@ Script.include("/~/system/libraries/controllers.js"); // is pointing at an entity. var entityProperties = Entities.getEntityProperties(intersection.objectID); var entityType = entityProperties.type; - if (entityIsGrabbable(entityProperties)) { + if (entityIsGrabbable(entityProperties) && entityType !== "Web") { // check if entity is near grabbable. var distance = Vec3.distance(entityProperties.position, controllerData.controllerLocations[this.hand].position); - if (distance <= NEAR_GRAB_RADIUS * MyAvatar.sensorToWorldScale) - return (triggerPressed && entityType !== "Web"); + if (distance <= NEAR_GRAB_RADIUS * MyAvatar.sensorToWorldScale) { + return triggerPressed; + } else { + // far-grabbable, but still return it as true anyway + return false; + } } } return false; @@ -90,25 +92,15 @@ Script.include("/~/system/libraries/controllers.js"); if ((HMD.tabletID && objectID === HMD.tabletID) || (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || (HMD.homeButtonID && objectID === HMD.homeButtonID)) { - this.lastObjectID = objectID; return true; } else { var overlayType = Overlays.getOverlayType(objectID); - this.lastObjectID = objectID; return overlayType === "web3d" || triggerPressed; } } else if (intersection.type === Picks.INTERSECTED_ENTITY) { var entityProperty = Entities.getEntityProperties(objectID); - if (entityProperty.type === "Web") { - var isLocked = entityProperty.locked; - this.lastObjectID = objectID; - return (!isLocked || triggerPressed); - } else if (this.ignoredEntities.indexOf(objectID) === -1 && triggerPressed && this.lastObjectID !== objectID) { - // ignore, preserve whether it's running or not. - console.log("I'm here"); - Pointers.setIgnoreItems(controllerData.pointers[this.hand], [objectID]); - this.ignoredEntities.push(objectID); - } + var isLocked = entityProperty.locked; + return (!isLocked || triggerPressed || entityProperty.type === "Web"); } return false; }; @@ -126,11 +118,6 @@ Script.include("/~/system/libraries/controllers.js"); } }; - this.reAddIgnoredEntities = function(controllerData) { - Pointers.setIncludeItems(controllerData.pointers[this.hand], this.ignoredEntities); - this.ignoredEntities = []; - } - this.updateAllwaysOn = function() { var PREFER_STYLUS_OVER_LASER = "preferStylusOverLaser"; this.parameters.handLaser.allwaysOn = !Settings.getValue(PREFER_STYLUS_OVER_LASER, false); @@ -148,8 +135,7 @@ Script.include("/~/system/libraries/controllers.js"); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; var allowThisModule = !otherModuleRunning || isTriggerPressed; - if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed) && - !this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)) { + if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. @@ -176,7 +162,6 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(true, [], []); } this.deleteContextOverlay(); - this.reAddIgnoredEntities(controllerData); this.running = false; this.dominantHandOverride = false; return makeRunningValues(false, [], []); From be5fdef974c66ec7a4525acddb07d48870b5adba Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 20 Jul 2018 15:26:55 -0700 Subject: [PATCH 007/744] adding check in isReady for grabbing non-web entitys --- .../controllers/controllerDispatcher.js | 48 ++++++++++++++++++- .../controllerModules/webSurfaceLaserInput.js | 19 ++++---- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 09d09248fc..8dbf4a6789 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -31,6 +31,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var PROFILE = false; var DEBUG = false; + var DEBUG_OVERLAY = true; if (typeof Test !== "undefined") { PROFILE = true; @@ -48,6 +49,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.tabletID = null; this.blacklist = []; this.pointerManager = new PointerManager(); + this.debugOverlayID = null; // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name @@ -298,7 +300,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue], triggerClicks: [_this.leftTriggerClicked, _this.rightTriggerClicked], secondaryValues: [_this.leftSecondaryValue, _this.rightSecondaryValue], - pointers: [_this.leftPointer, _this.rightPointer ], controllerLocations: controllerLocations, nearbyEntityProperties: nearbyEntityProperties, nearbyEntityPropertiesByID: nearbyEntityPropertiesByID, @@ -378,6 +379,48 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } } _this.pointerManager.updatePointersRenderState(controllerData.triggerClicks, controllerData.triggerValues); + + if (DEBUG_OVERLAY) { + if (!_this.debugOverlayID) { + + var textWidth = 0.4; + var textHeight = 0.3; + var numberOfLines = 15; + var textMargin = 0.02; + var lineHeight = (textHeight - (2 * textMargin)) / numberOfLines; + + _this.debugOverlayID = Overlays.addOverlay("text3d", { + localPosition: { x: 0.3, y: 0.2, z: 1.1 }, + localRotation: { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }, + parentID: MyAvatar.sessionUUID, + parentJointIndex: MyAvatar.getJointIndex("Head"), + dimensions: { x: textWidth, y: textHeight }, + backgroundColor: { red: 0, green: 0, blue: 0 }, + color: { red: 255, green: 255, blue: 255 }, + topMargin: textMargin, + leftMargin: textMargin, + bottomMargin: textMargin, + rightMargin: textMargin, + text: "", + lineHeight: lineHeight, + alpha: 0.9, + backgroundAlpha: 0.9, + ignoreRayIntersection: true, + visible: true, + isFacingAvatar: true + }); + } + + var debugText = ""; + Object.keys(_this.runningPluginNames).forEach(function (pluginName) { + if (_this.runningPluginNames[pluginName]) { + var plugin = controllerDispatcherPlugins[pluginName]; + debugText += pluginName + ": " + plugin.parameters.priority + "\n"; + } + }); + Overlays.editOverlay(_this.debugOverlayID, { text: debugText }); + } + if (PROFILE) { Script.endProfileRange("dispatch.run"); } @@ -481,6 +524,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.cleanup = function () { Controller.disableMapping(MAPPING_NAME); _this.pointerManager.removePointers(); + if (_this.debugOverlayID) { + Overlays.deleteOverlay(_this.debugOverlayID); + } Pointers.removePointer(this.mouseRayPick); Selection.disableListHighlight(DISPATCHER_HOVERING_LIST); }; diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 16e2c1a4f7..16cf2eef5c 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -5,12 +5,11 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Script, Entities, Controller, RIGHT_HAND, LEFT_HAND, getControllerWorldLocation, - enableDispatcherModule, disableDispatcherModule, makeRunningValues, Messages, Quat, Vec3, - makeDispatcherModuleParameters, Overlays, ZERO_VEC, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, - getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, - COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, TRIGGER_OFF_VALUE, - getEnabledModuleByName, PICK_MAX_DISTANCE, ContextOverlay, Picks, makeLaserParams +/* global Script, Entities, Controller, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, + makeRunningValues, Messages, Quat, Vec3, makeDispatcherModuleParameters, Overlays, ZERO_VEC, HMD, + INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, + TRIGGER_OFF_VALUE, getEnabledModuleByName, PICK_MAX_DISTANCE, ContextOverlay, Picks, makeLaserParams */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -76,7 +75,7 @@ Script.include("/~/system/libraries/controllers.js"); return triggerPressed; } else { // far-grabbable, but still return it as true anyway - return false; + return true; } } } @@ -99,8 +98,9 @@ Script.include("/~/system/libraries/controllers.js"); } } else if (intersection.type === Picks.INTERSECTED_ENTITY) { var entityProperty = Entities.getEntityProperties(objectID); + var entityType = entityProperty.type; var isLocked = entityProperty.locked; - return (!isLocked || triggerPressed || entityProperty.type === "Web"); + return (!isLocked || triggerPressed || entityType === "Web"); } return false; }; @@ -135,7 +135,8 @@ Script.include("/~/system/libraries/controllers.js"); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; var allowThisModule = !otherModuleRunning || isTriggerPressed; - if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) { + if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed) && + !this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. From e9ab2b8f27db38a81f114c89fd0d66308c76d1c9 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 20 Jul 2018 16:48:15 -0700 Subject: [PATCH 008/744] enable LOD manager on android --- interface/src/Application.cpp | 2 -- interface/src/LODManager.h | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 311c08b858..0da3d9445e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5803,9 +5803,7 @@ void Application::update(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::update()"); -#if !defined(Q_OS_ANDROID) updateLOD(deltaTime); -#endif // TODO: break these out into distinct perfTimers when they prove interesting { diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 8f88da63a8..8cae179f1e 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -19,7 +19,11 @@ #include #include +#ifdef Q_OS_ANDROID +const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 10.0f; +#else const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f; +#endif const float DEFAULT_HMD_LOD_DOWN_FPS = 34.0f; const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec From 32886d67d98b7d2b964b25e39ce1a821a05593fe Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 20 Jul 2018 17:17:36 -0700 Subject: [PATCH 009/744] optimizing near-/far- grabbable entities a bit --- .../controllers/controllerDispatcher.js | 74 +++---- .../system/controllers/controllerModules/' | 181 ++++++++++++++++++ .../controllerModules/webSurfaceLaserInput.js | 12 +- 3 files changed, 223 insertions(+), 44 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/' diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 8dbf4a6789..399ccf0ba3 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -380,46 +380,46 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } _this.pointerManager.updatePointersRenderState(controllerData.triggerClicks, controllerData.triggerValues); - if (DEBUG_OVERLAY) { - if (!_this.debugOverlayID) { +/* if (DEBUG_OVERLAY) {*/ + //if (!_this.debugOverlayID) { - var textWidth = 0.4; - var textHeight = 0.3; - var numberOfLines = 15; - var textMargin = 0.02; - var lineHeight = (textHeight - (2 * textMargin)) / numberOfLines; + //var textWidth = 0.4; + //var textHeight = 0.3; + //var numberOfLines = 15; + //var textMargin = 0.02; + //var lineHeight = (textHeight - (2 * textMargin)) / numberOfLines; - _this.debugOverlayID = Overlays.addOverlay("text3d", { - localPosition: { x: 0.3, y: 0.2, z: 1.1 }, - localRotation: { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }, - parentID: MyAvatar.sessionUUID, - parentJointIndex: MyAvatar.getJointIndex("Head"), - dimensions: { x: textWidth, y: textHeight }, - backgroundColor: { red: 0, green: 0, blue: 0 }, - color: { red: 255, green: 255, blue: 255 }, - topMargin: textMargin, - leftMargin: textMargin, - bottomMargin: textMargin, - rightMargin: textMargin, - text: "", - lineHeight: lineHeight, - alpha: 0.9, - backgroundAlpha: 0.9, - ignoreRayIntersection: true, - visible: true, - isFacingAvatar: true - }); - } + //_this.debugOverlayID = Overlays.addOverlay("text3d", { + //localPosition: { x: 0.3, y: 0.2, z: 1.1 }, + //localRotation: { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }, + //parentID: MyAvatar.sessionUUID, + //parentJointIndex: MyAvatar.getJointIndex("Head"), + //dimensions: { x: textWidth, y: textHeight }, + //backgroundColor: { red: 0, green: 0, blue: 0 }, + //color: { red: 255, green: 255, blue: 255 }, + //topMargin: textMargin, + //leftMargin: textMargin, + //bottomMargin: textMargin, + //rightMargin: textMargin, + //text: "", + //lineHeight: lineHeight, + //alpha: 0.9, + //backgroundAlpha: 0.9, + //ignoreRayIntersection: true, + //visible: true, + //isFacingAvatar: true + //}); + //} - var debugText = ""; - Object.keys(_this.runningPluginNames).forEach(function (pluginName) { - if (_this.runningPluginNames[pluginName]) { - var plugin = controllerDispatcherPlugins[pluginName]; - debugText += pluginName + ": " + plugin.parameters.priority + "\n"; - } - }); - Overlays.editOverlay(_this.debugOverlayID, { text: debugText }); - } + //var debugText = ""; + //Object.keys(_this.runningPluginNames).forEach(function (pluginName) { + //if (_this.runningPluginNames[pluginName]) { + //var plugin = controllerDispatcherPlugins[pluginName]; + //debugText += pluginName + ": " + plugin.parameters.priority + "\n"; + //} + //}); + //Overlays.editOverlay(_this.debugOverlayID, { text: debugText }); + /*}*/ if (PROFILE) { Script.endProfileRange("dispatch.run"); diff --git a/scripts/system/controllers/controllerModules/' b/scripts/system/controllers/controllerModules/' new file mode 100644 index 0000000000..8217e0d022 --- /dev/null +++ b/scripts/system/controllers/controllerModules/' @@ -0,0 +1,181 @@ +"use strict"; + +// webSurfaceLaserInput.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Script, Entities, Controller, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, + makeRunningValues, Messages, Quat, Vec3, makeDispatcherModuleParameters, Overlays, ZERO_VEC, HMD, + INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, + TRIGGER_OFF_VALUE, getEnabledModuleByName, PICK_MAX_DISTANCE, ContextOverlay, Picks, makeLaserParams +*/ + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + function WebSurfaceLaserInput(hand) { + this.hand = hand; + this.otherHand = this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND; + this.running = false; + + this.parameters = makeDispatcherModuleParameters( + 160, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100, + makeLaserParams(hand, true)); + + this.grabModuleWantsNearbyOverlay = function(controllerData) { + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + var nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; + var nearGrabModule = getEnabledModuleByName(nearGrabName); + if (nearGrabModule) { + var candidateOverlays = controllerData.nearbyOverlayIDs[this.hand]; + var grabbableOverlays = candidateOverlays.filter(function(overlayID) { + return Overlays.getProperty(overlayID, "grabbable"); + }); + var target = nearGrabModule.getTargetID(grabbableOverlays, controllerData); + if (target) { + return true; + } + } + nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity"; + nearGrabModule = getEnabledModuleByName(nearGrabName); + if (nearGrabModule && nearGrabModule.isReady(controllerData)) { + return nearGrabModule.isReady(controllerData).active; + } + nearGrabName = this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity"; + nearGrabModule = getEnabledModuleByName(nearGrabName); + if (nearGrabModule && nearGrabModule.isReady(controllerData)) { + return nearGrabModule.isReady(controllerData).active; + } + } + return false; + }; + + this.getOtherModule = function() { + return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput; + }; + + this.isPointingAtNearGrabbableEntity = function(controllerData, triggerPressed) { + // we are searching for an entity that is not a web entity. We want to be able to + // grab a non-web entity if the ray-pick intersects one. + var intersection = controllerData.rayPicks[this.hand]; + if (intersection.type === Picks.INTERSECTED_ENTITY) { + // is pointing at an entity. + var entityProperties = Entities.getEntityProperties(intersection.objectID); + var entityType = entityProperties.type; + if (entityIsGrabbable(entityProperties) && entityType !== "Web") { + // check if entity is near grabbable. + var distance = Vec3.distance(entityProperties.position, controllerData.controllerLocations[this.hand].position); + if (distance >= NEAR_GRAB_RADIUS * MyAvatar.sensorToWorldScale) { + // far-grabbable, but still return it as true anyway + return true; + } + } + } + return false; + }; + + this.isPointingAtTriggerable = function(controllerData, triggerPressed) { + // allow pointing at tablet, unlocked web entities, or web overlays automatically without pressing trigger, + // but for pointing at locked web entities or non-web overlays user must be pressing trigger + var intersection = controllerData.rayPicks[this.hand]; + var objectID = intersection.objectID; + if (intersection.type === Picks.INTERSECTED_OVERLAY) { + if ((HMD.tabletID && objectID === HMD.tabletID) || + (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || + (HMD.homeButtonID && objectID === HMD.homeButtonID)) { + return true; + } else { + var overlayType = Overlays.getOverlayType(objectID); + return overlayType === "web3d" || triggerPressed; + } + } else if (intersection.type === Picks.INTERSECTED_ENTITY) { + var entityProperty = Entities.getEntityProperties(objectID); + var entityType = entityProperty.type; + var isLocked = entityProperty.locked; + return (!isLocked || triggerPressed || entityType === "Web"); + } + return false; + }; + + this.deleteContextOverlay = function() { + var farGrabModule = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity"); + if (farGrabModule) { + var entityWithContextOverlay = farGrabModule.entityWithContextOverlay; + + if (entityWithContextOverlay) { + ContextOverlay.destroyContextOverlay(entityWithContextOverlay); + farGrabModule.entityWithContextOverlay = false; + } + } + }; + + this.updateAllwaysOn = function() { + var PREFER_STYLUS_OVER_LASER = "preferStylusOverLaser"; + this.parameters.handLaser.allwaysOn = !Settings.getValue(PREFER_STYLUS_OVER_LASER, false); + }; + + this.getDominantHand = function() { + return MyAvatar.getDominantHand() === "right" ? 1 : 0; + }; + + this.dominantHandOverride = false; + + this.isReady = function(controllerData) { + var otherModuleRunning = this.getOtherModule().running; + otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. + var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && + controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; + var allowThisModule = !otherModuleRunning || isTriggerPressed; + if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed) && + (controllerData.nearbyEntityProperties[this.hand] !== [])) { + this.updateAllwaysOn(); + if (isTriggerPressed) { + this.dominantHandOverride = true; // Override dominant hand. + this.getOtherModule().dominantHandOverride = false; + } + if (this.parameters.handLaser.allwaysOn || isTriggerPressed) { + return makeRunningValues(true, [], []); + } + } + return makeRunningValues(false, [], []); + }; + + this.run = function(controllerData, deltaTime) { + var otherModuleRunning = this.getOtherModule().running; + otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. + otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand. + var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData); + var allowThisModule = !otherModuleRunning && !grabModuleNeedsToRun; + var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; + var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; + if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) && + (controllerData.nearbyEntityProperties[this.hand] !== [] || (this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)))) { + this.running = true; + return makeRunningValues(true, [], []); + } + this.deleteContextOverlay(); + this.running = false; + this.dominantHandOverride = false; + return makeRunningValues(false, [], []); + }; + } + + var leftOverlayLaserInput = new WebSurfaceLaserInput(LEFT_HAND); + var rightOverlayLaserInput = new WebSurfaceLaserInput(RIGHT_HAND); + + enableDispatcherModule("LeftWebSurfaceLaserInput", leftOverlayLaserInput); + enableDispatcherModule("RightWebSurfaceLaserInput", rightOverlayLaserInput); + + function cleanup() { + disableDispatcherModule("LeftWebSurfaceLaserInput"); + disableDispatcherModule("RightWebSurfaceLaserInput"); + } + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 16cf2eef5c..dd7bccf667 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -60,7 +60,7 @@ Script.include("/~/system/libraries/controllers.js"); return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput; }; - this.isPointingAtNearGrabbableEntity = function(controllerData, triggerPressed) { + this.isPointingAtFarGrabbableEntity = function(controllerData, triggerPressed) { // we are searching for an entity that is not a web entity. We want to be able to // grab a non-web entity if the ray-pick intersects one. var intersection = controllerData.rayPicks[this.hand]; @@ -71,9 +71,7 @@ Script.include("/~/system/libraries/controllers.js"); if (entityIsGrabbable(entityProperties) && entityType !== "Web") { // check if entity is near grabbable. var distance = Vec3.distance(entityProperties.position, controllerData.controllerLocations[this.hand].position); - if (distance <= NEAR_GRAB_RADIUS * MyAvatar.sensorToWorldScale) { - return triggerPressed; - } else { + if (distance >= NEAR_GRAB_RADIUS * MyAvatar.sensorToWorldScale) { // far-grabbable, but still return it as true anyway return true; } @@ -135,8 +133,8 @@ Script.include("/~/system/libraries/controllers.js"); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; var allowThisModule = !otherModuleRunning || isTriggerPressed; - if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed) && - !this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)) { + if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed) && + (controllerData.nearbyEntityProperties[this.hand] !== [])) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. @@ -158,7 +156,7 @@ Script.include("/~/system/libraries/controllers.js"); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) && - !this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)) { + (controllerData.nearbyEntityProperties[this.hand] !== [] || !(this.isPointingAtFarGrabbableEntity(controllerData, isTriggerPressed)))) { this.running = true; return makeRunningValues(true, [], []); } From 1880c49d0358b535156d00671499f14fddab2979 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 20 Jul 2018 17:18:58 -0700 Subject: [PATCH 010/744] removing garbage file --- .../system/controllers/controllerModules/' | 181 ------------------ 1 file changed, 181 deletions(-) delete mode 100644 scripts/system/controllers/controllerModules/' diff --git a/scripts/system/controllers/controllerModules/' b/scripts/system/controllers/controllerModules/' deleted file mode 100644 index 8217e0d022..0000000000 --- a/scripts/system/controllers/controllerModules/' +++ /dev/null @@ -1,181 +0,0 @@ -"use strict"; - -// webSurfaceLaserInput.js -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - -/* global Script, Entities, Controller, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, - makeRunningValues, Messages, Quat, Vec3, makeDispatcherModuleParameters, Overlays, ZERO_VEC, HMD, - INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, - COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, - TRIGGER_OFF_VALUE, getEnabledModuleByName, PICK_MAX_DISTANCE, ContextOverlay, Picks, makeLaserParams -*/ - -Script.include("/~/system/libraries/controllerDispatcherUtils.js"); -Script.include("/~/system/libraries/controllers.js"); - -(function() { - function WebSurfaceLaserInput(hand) { - this.hand = hand; - this.otherHand = this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND; - this.running = false; - - this.parameters = makeDispatcherModuleParameters( - 160, - this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], - [], - 100, - makeLaserParams(hand, true)); - - this.grabModuleWantsNearbyOverlay = function(controllerData) { - if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - var nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; - var nearGrabModule = getEnabledModuleByName(nearGrabName); - if (nearGrabModule) { - var candidateOverlays = controllerData.nearbyOverlayIDs[this.hand]; - var grabbableOverlays = candidateOverlays.filter(function(overlayID) { - return Overlays.getProperty(overlayID, "grabbable"); - }); - var target = nearGrabModule.getTargetID(grabbableOverlays, controllerData); - if (target) { - return true; - } - } - nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity"; - nearGrabModule = getEnabledModuleByName(nearGrabName); - if (nearGrabModule && nearGrabModule.isReady(controllerData)) { - return nearGrabModule.isReady(controllerData).active; - } - nearGrabName = this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity"; - nearGrabModule = getEnabledModuleByName(nearGrabName); - if (nearGrabModule && nearGrabModule.isReady(controllerData)) { - return nearGrabModule.isReady(controllerData).active; - } - } - return false; - }; - - this.getOtherModule = function() { - return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput; - }; - - this.isPointingAtNearGrabbableEntity = function(controllerData, triggerPressed) { - // we are searching for an entity that is not a web entity. We want to be able to - // grab a non-web entity if the ray-pick intersects one. - var intersection = controllerData.rayPicks[this.hand]; - if (intersection.type === Picks.INTERSECTED_ENTITY) { - // is pointing at an entity. - var entityProperties = Entities.getEntityProperties(intersection.objectID); - var entityType = entityProperties.type; - if (entityIsGrabbable(entityProperties) && entityType !== "Web") { - // check if entity is near grabbable. - var distance = Vec3.distance(entityProperties.position, controllerData.controllerLocations[this.hand].position); - if (distance >= NEAR_GRAB_RADIUS * MyAvatar.sensorToWorldScale) { - // far-grabbable, but still return it as true anyway - return true; - } - } - } - return false; - }; - - this.isPointingAtTriggerable = function(controllerData, triggerPressed) { - // allow pointing at tablet, unlocked web entities, or web overlays automatically without pressing trigger, - // but for pointing at locked web entities or non-web overlays user must be pressing trigger - var intersection = controllerData.rayPicks[this.hand]; - var objectID = intersection.objectID; - if (intersection.type === Picks.INTERSECTED_OVERLAY) { - if ((HMD.tabletID && objectID === HMD.tabletID) || - (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || - (HMD.homeButtonID && objectID === HMD.homeButtonID)) { - return true; - } else { - var overlayType = Overlays.getOverlayType(objectID); - return overlayType === "web3d" || triggerPressed; - } - } else if (intersection.type === Picks.INTERSECTED_ENTITY) { - var entityProperty = Entities.getEntityProperties(objectID); - var entityType = entityProperty.type; - var isLocked = entityProperty.locked; - return (!isLocked || triggerPressed || entityType === "Web"); - } - return false; - }; - - this.deleteContextOverlay = function() { - var farGrabModule = getEnabledModuleByName(this.hand === RIGHT_HAND - ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity"); - if (farGrabModule) { - var entityWithContextOverlay = farGrabModule.entityWithContextOverlay; - - if (entityWithContextOverlay) { - ContextOverlay.destroyContextOverlay(entityWithContextOverlay); - farGrabModule.entityWithContextOverlay = false; - } - } - }; - - this.updateAllwaysOn = function() { - var PREFER_STYLUS_OVER_LASER = "preferStylusOverLaser"; - this.parameters.handLaser.allwaysOn = !Settings.getValue(PREFER_STYLUS_OVER_LASER, false); - }; - - this.getDominantHand = function() { - return MyAvatar.getDominantHand() === "right" ? 1 : 0; - }; - - this.dominantHandOverride = false; - - this.isReady = function(controllerData) { - var otherModuleRunning = this.getOtherModule().running; - otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. - var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && - controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; - var allowThisModule = !otherModuleRunning || isTriggerPressed; - if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed) && - (controllerData.nearbyEntityProperties[this.hand] !== [])) { - this.updateAllwaysOn(); - if (isTriggerPressed) { - this.dominantHandOverride = true; // Override dominant hand. - this.getOtherModule().dominantHandOverride = false; - } - if (this.parameters.handLaser.allwaysOn || isTriggerPressed) { - return makeRunningValues(true, [], []); - } - } - return makeRunningValues(false, [], []); - }; - - this.run = function(controllerData, deltaTime) { - var otherModuleRunning = this.getOtherModule().running; - otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. - otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand. - var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData); - var allowThisModule = !otherModuleRunning && !grabModuleNeedsToRun; - var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; - var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; - if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) && - (controllerData.nearbyEntityProperties[this.hand] !== [] || (this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)))) { - this.running = true; - return makeRunningValues(true, [], []); - } - this.deleteContextOverlay(); - this.running = false; - this.dominantHandOverride = false; - return makeRunningValues(false, [], []); - }; - } - - var leftOverlayLaserInput = new WebSurfaceLaserInput(LEFT_HAND); - var rightOverlayLaserInput = new WebSurfaceLaserInput(RIGHT_HAND); - - enableDispatcherModule("LeftWebSurfaceLaserInput", leftOverlayLaserInput); - enableDispatcherModule("RightWebSurfaceLaserInput", rightOverlayLaserInput); - - function cleanup() { - disableDispatcherModule("LeftWebSurfaceLaserInput"); - disableDispatcherModule("RightWebSurfaceLaserInput"); - } - Script.scriptEnding.connect(cleanup); -}()); From a2aeacab649d77acc8399291f719ea7b0f16c758 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 23 Jul 2018 12:17:19 -0700 Subject: [PATCH 011/744] adding fix for near grabbing with web entity --- .../controllerModules/webSurfaceLaserInput.js | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index dd7bccf667..5009bec0f5 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -60,25 +60,14 @@ Script.include("/~/system/libraries/controllers.js"); return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput; }; - this.isPointingAtFarGrabbableEntity = function(controllerData, triggerPressed) { - // we are searching for an entity that is not a web entity. We want to be able to - // grab a non-web entity if the ray-pick intersects one. + this.isPointingAtNearGrabbableEntity = function(controllerData, triggerPressed) { var intersection = controllerData.rayPicks[this.hand]; - if (intersection.type === Picks.INTERSECTED_ENTITY) { - // is pointing at an entity. - var entityProperties = Entities.getEntityProperties(intersection.objectID); - var entityType = entityProperties.type; - if (entityIsGrabbable(entityProperties) && entityType !== "Web") { - // check if entity is near grabbable. - var distance = Vec3.distance(entityProperties.position, controllerData.controllerLocations[this.hand].position); - if (distance >= NEAR_GRAB_RADIUS * MyAvatar.sensorToWorldScale) { - // far-grabbable, but still return it as true anyway - return true; - } - } + var objectID = intersection.objectID; + if(intersection.type === Picks.INTERSECTED_ENTITY) { + return (controllerData.nearbyEntityPropertiesByID[objectID] !== null && triggerPressed); } return false; - }; + } this.isPointingAtTriggerable = function(controllerData, triggerPressed) { // allow pointing at tablet, unlocked web entities, or web overlays automatically without pressing trigger, @@ -98,7 +87,7 @@ Script.include("/~/system/libraries/controllers.js"); var entityProperty = Entities.getEntityProperties(objectID); var entityType = entityProperty.type; var isLocked = entityProperty.locked; - return (!isLocked || triggerPressed || entityType === "Web"); + return entityType === "Web" && (!isLocked || triggerPressed); } return false; }; @@ -156,7 +145,7 @@ Script.include("/~/system/libraries/controllers.js"); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) && - (controllerData.nearbyEntityProperties[this.hand] !== [] || !(this.isPointingAtFarGrabbableEntity(controllerData, isTriggerPressed)))) { + (controllerData.nearbyEntityProperties[this.hand] !== [])) { this.running = true; return makeRunningValues(true, [], []); } From 1ec136a19c04a3f9e39ee7f691667a3713eb1231 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 23 Jul 2018 12:18:26 -0700 Subject: [PATCH 012/744] removing debug overlay statements --- .../controllers/controllerDispatcher.js | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 399ccf0ba3..91ab824a54 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -31,7 +31,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var PROFILE = false; var DEBUG = false; - var DEBUG_OVERLAY = true; if (typeof Test !== "undefined") { PROFILE = true; @@ -380,47 +379,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } _this.pointerManager.updatePointersRenderState(controllerData.triggerClicks, controllerData.triggerValues); -/* if (DEBUG_OVERLAY) {*/ - //if (!_this.debugOverlayID) { - - //var textWidth = 0.4; - //var textHeight = 0.3; - //var numberOfLines = 15; - //var textMargin = 0.02; - //var lineHeight = (textHeight - (2 * textMargin)) / numberOfLines; - - //_this.debugOverlayID = Overlays.addOverlay("text3d", { - //localPosition: { x: 0.3, y: 0.2, z: 1.1 }, - //localRotation: { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }, - //parentID: MyAvatar.sessionUUID, - //parentJointIndex: MyAvatar.getJointIndex("Head"), - //dimensions: { x: textWidth, y: textHeight }, - //backgroundColor: { red: 0, green: 0, blue: 0 }, - //color: { red: 255, green: 255, blue: 255 }, - //topMargin: textMargin, - //leftMargin: textMargin, - //bottomMargin: textMargin, - //rightMargin: textMargin, - //text: "", - //lineHeight: lineHeight, - //alpha: 0.9, - //backgroundAlpha: 0.9, - //ignoreRayIntersection: true, - //visible: true, - //isFacingAvatar: true - //}); - //} - - //var debugText = ""; - //Object.keys(_this.runningPluginNames).forEach(function (pluginName) { - //if (_this.runningPluginNames[pluginName]) { - //var plugin = controllerDispatcherPlugins[pluginName]; - //debugText += pluginName + ": " + plugin.parameters.priority + "\n"; - //} - //}); - //Overlays.editOverlay(_this.debugOverlayID, { text: debugText }); - /*}*/ - if (PROFILE) { Script.endProfileRange("dispatch.run"); } @@ -524,9 +482,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.cleanup = function () { Controller.disableMapping(MAPPING_NAME); _this.pointerManager.removePointers(); - if (_this.debugOverlayID) { - Overlays.deleteOverlay(_this.debugOverlayID); - } Pointers.removePointer(this.mouseRayPick); Selection.disableListHighlight(DISPATCHER_HOVERING_LIST); }; From e4a5be668a15aa603e3210b2053c2bc62ad2536a Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 23 Jul 2018 12:29:01 -0700 Subject: [PATCH 013/744] adding check function for pointing at near grabbable entity --- scripts/system/controllers/controllerDispatcher.js | 2 -- .../controllers/controllerModules/webSurfaceLaserInput.js | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 91ab824a54..4002fd297b 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -48,7 +48,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.tabletID = null; this.blacklist = []; this.pointerManager = new PointerManager(); - this.debugOverlayID = null; // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name @@ -378,7 +377,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } } _this.pointerManager.updatePointersRenderState(controllerData.triggerClicks, controllerData.triggerValues); - if (PROFILE) { Script.endProfileRange("dispatch.run"); } diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 5009bec0f5..92423f0670 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -123,7 +123,7 @@ Script.include("/~/system/libraries/controllers.js"); controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; var allowThisModule = !otherModuleRunning || isTriggerPressed; if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed) && - (controllerData.nearbyEntityProperties[this.hand] !== [])) { + !this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. @@ -145,7 +145,7 @@ Script.include("/~/system/libraries/controllers.js"); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) && - (controllerData.nearbyEntityProperties[this.hand] !== [])) { + !this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)) { this.running = true; return makeRunningValues(true, [], []); } From c0dfbac480ab9cbed8f7d2e90197a903439639ce Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 24 Jul 2018 10:04:51 -0700 Subject: [PATCH 014/744] adding comment for possible solution - still WIP --- .../controllers/controllerModules/webSurfaceLaserInput.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 92423f0670..ef9ff0c6ae 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -124,6 +124,7 @@ Script.include("/~/system/libraries/controllers.js"); var allowThisModule = !otherModuleRunning || isTriggerPressed; if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed) && !this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)) { + //(controllerData.nearbyEntityProperties[this.hand] !== [])) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. @@ -146,6 +147,7 @@ Script.include("/~/system/libraries/controllers.js"); var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) && !this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)) { + //(controllerData.nearbyEntityProperties[this.hand] !== [])) { this.running = true; return makeRunningValues(true, [], []); } From 78001a4e31d1e53b36d31b3a342b965ee839ee8c Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 30 Jul 2018 18:44:39 -0700 Subject: [PATCH 015/744] adding fix --- scripts/developer/utilities/audio/Stats.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/developer/utilities/audio/Stats.qml b/scripts/developer/utilities/audio/Stats.qml index 7f559ea664..e2cf028779 100644 --- a/scripts/developer/utilities/audio/Stats.qml +++ b/scripts/developer/utilities/audio/Stats.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "../../../../resources/qml/controls-uit" as HifiControls +import "qrc:////qml//controls-uit" as HifiControls Column { id: stats @@ -25,7 +25,7 @@ Column { HifiControls.Button { id: toggleGraphs - property bool checked: false + checked: false anchors.horizontalCenter: parent.horizontalCenter text: checked ? "Hide graphs" : "Show graphs" onClicked: function() { checked = !checked; } From b020e17ee70fd700638801300f2cc4eed2704fa2 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 31 Jul 2018 10:46:19 -0700 Subject: [PATCH 016/744] rectifying toggle graph behavior --- scripts/developer/utilities/audio/Stats.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/developer/utilities/audio/Stats.qml b/scripts/developer/utilities/audio/Stats.qml index e2cf028779..57963732c8 100644 --- a/scripts/developer/utilities/audio/Stats.qml +++ b/scripts/developer/utilities/audio/Stats.qml @@ -17,7 +17,7 @@ import "qrc:////qml//controls-uit" as HifiControls Column { id: stats width: parent.width - property bool showGraphs: toggleGraphs.checked + property alias showGraphs: toggleGraphs.toggle Item { width: parent.width @@ -25,10 +25,10 @@ Column { HifiControls.Button { id: toggleGraphs - checked: false + property bool toggle: false anchors.horizontalCenter: parent.horizontalCenter - text: checked ? "Hide graphs" : "Show graphs" - onClicked: function() { checked = !checked; } + text: toggle ? "Hide graphs" : "Show graphs" + onClicked: { toggle = !toggle;} } } From f19a553e270b4c76afd245168aff7bd0e8a0b573 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 31 Jul 2018 10:49:30 -0700 Subject: [PATCH 017/744] adding fix for HMD mode --- scripts/developer/utilities/audio/TabletStats.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/developer/utilities/audio/TabletStats.qml b/scripts/developer/utilities/audio/TabletStats.qml index 130b90f032..2f8d212a2a 100644 --- a/scripts/developer/utilities/audio/TabletStats.qml +++ b/scripts/developer/utilities/audio/TabletStats.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "../../../../resources/qml/styles-uit" +import "qrc:////qml//styles-uit" Item { id: dialog From 0b7d6cb7203a60e7f4b31154b4de8db01c498028 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 31 Jul 2018 14:10:19 -0700 Subject: [PATCH 018/744] start interstitial page work --- interface/src/Application.cpp | 12 ++++++++++++ interface/src/Application.h | 3 +++ libraries/audio-client/src/AudioClient.cpp | 8 ++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 136e44855a..b638c51342 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3412,6 +3412,16 @@ bool Application::isServerlessMode() const { return false; } +bool Application::isInterstitialPage() { + return _interstitialMode; +} + +void Application::setInterstitialMode(bool interstitialMode) { + if (_interstitialMode != interstitialMode) { + _interstitialMode = interstitialMode; + } +} + void Application::setIsServerlessMode(bool serverlessDomain) { auto tree = getEntities()->getTree(); if (tree) { @@ -5471,6 +5481,8 @@ static bool domainLoadingInProgress = false; void Application::update(float deltaTime) { PROFILE_RANGE_EX(app, __FUNCTION__, 0xffff0000, (uint64_t)_renderFrameCount + 1); + auto audioClient = DependencyManager::get(); + audioClient->setMuted(true); if (!_physicsEnabled) { if (!domainLoadingInProgress) { PROFILE_ASYNC_BEGIN(app, "Scene Loading", ""); diff --git a/interface/src/Application.h b/interface/src/Application.h index 94e561e550..c084c0033f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -304,6 +304,7 @@ public: void saveNextPhysicsStats(QString filename); bool isServerlessMode() const; + bool isInterstitialMode() const; void replaceDomainContent(const QString& url); @@ -423,6 +424,7 @@ public slots: void setPreferredCursor(const QString& cursor); void setIsServerlessMode(bool serverlessDomain); + void setIsInterstitialMode(bool interstialMode); void loadServerlessDomain(QUrl domainURL); void updateVerboseLogging(); @@ -624,6 +626,7 @@ private: QSet _keysPressed; bool _enableProcessOctreeThread; + bool _interstitialMode { true }; OctreePacketProcessor _octreeProcessor; EntityEditPacketSender _entityEditSender; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c57360b09f..0eadcc5b66 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -668,7 +668,7 @@ void AudioClient::stop() { void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { - char bitset; + /*char bitset; message->readPrimitive(&bitset); bool hasReverb = oneAtBit(bitset, HAS_REVERB_BIT); @@ -680,11 +680,11 @@ void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { - + /* if (message->getType() == PacketType::SilentAudioFrame) { _silentInbound.increment(); } else { @@ -709,7 +709,7 @@ void AudioClient::handleAudioDataPacket(QSharedPointer message) // Audio output must exist and be correctly set up if we're going to process received audio _receivedAudioStream.parseData(*message); #endif - } +}*/ } AudioClient::Gate::Gate(AudioClient* audioClient) : From 048196ec6fb2d56b319509c6dd2c2959e82a9135 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 2 Aug 2018 11:39:06 -0700 Subject: [PATCH 019/744] disble sending avatar and audio packets during interstitial mode --- interface/src/Application.cpp | 61 ++++++--- interface/src/Application.h | 1 + interface/src/avatar/AvatarManager.cpp | 6 +- interface/src/avatar/MyAvatar.cpp | 1 + libraries/audio-client/src/AudioClient.cpp | 152 ++++++++++----------- libraries/audio-client/src/AudioClient.h | 2 + 6 files changed, 125 insertions(+), 98 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b638c51342..bbce49b941 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1372,6 +1372,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); + connect(this, &Application::interstitialModeChanged, audioIO.data(), &AudioClient::setInterstitialStatus); } // Create the rendering engine. This can be slow on some machines due to lots of @@ -2252,6 +2253,25 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Preload Tablet sounds DependencyManager::get()->preloadSounds(); + connect(this, &Application::interstitialModeChanged, this, [this] (bool interstitialMode) { + if (!interstitialMode) { + DependencyManager::get()->negotiateAudioFormat(); + _queryExpiry = SteadyClock::now(); + if (_avatarOverrideUrl.isValid()) { + getMyAvatar()->useFullAvatarURL(_avatarOverrideUrl); + } + static const QUrl empty{}; + if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->cannonicalSkeletonModelURL(empty)) { + getMyAvatar()->resetFullAvatarURL(); + } + getMyAvatar()->markIdentityDataChanged(); + getMyAvatar()->resetLastSent(); + + // transmit a "sendAll" packet to the AvatarMixer we just connected to. + getMyAvatar()->sendAvatarDataPacket(true); + } + }); + _pendingIdleEvent = false; _pendingRenderEvent = false; @@ -3412,13 +3432,14 @@ bool Application::isServerlessMode() const { return false; } -bool Application::isInterstitialPage() { +bool Application::isInterstitialMode() const { return _interstitialMode; } -void Application::setInterstitialMode(bool interstitialMode) { +void Application::setIsInterstitialMode(bool interstitialMode) { if (_interstitialMode != interstitialMode) { _interstitialMode = interstitialMode; + emit interstitialModeChanged(_interstitialMode); } } @@ -5481,8 +5502,6 @@ static bool domainLoadingInProgress = false; void Application::update(float deltaTime) { PROFILE_RANGE_EX(app, __FUNCTION__, 0xffff0000, (uint64_t)_renderFrameCount + 1); - auto audioClient = DependencyManager::get(); - audioClient->setMuted(true); if (!_physicsEnabled) { if (!domainLoadingInProgress) { PROFILE_ASYNC_BEGIN(app, "Scene Loading", ""); @@ -5504,6 +5523,7 @@ void Application::update(float deltaTime) { // scene is ready to compute its collision shape. if (nearbyEntitiesAreReadyForPhysics() && getMyAvatar()->isReadyForPhysics()) { _physicsEnabled = true; + setIsInterstitialMode(false); getMyAvatar()->updateMotionBehaviorFromMenu(); } } @@ -5909,7 +5929,7 @@ void Application::update(float deltaTime) { // send packet containing downstream audio stats to the AudioMixer { quint64 sinceLastNack = now - _lastSendDownstreamAudioStats; - if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) { + if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS && !isInterstitialMode()) { _lastSendDownstreamAudioStats = now; QMetaObject::invokeMethod(DependencyManager::get().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); @@ -6072,21 +6092,23 @@ void Application::updateRenderArgs(float deltaTime) { } void Application::queryAvatars() { - auto avatarPacket = NLPacket::create(PacketType::AvatarQuery); - auto destinationBuffer = reinterpret_cast(avatarPacket->getPayload()); - unsigned char* bufferStart = destinationBuffer; + if (!isInterstitialMode()) { + auto avatarPacket = NLPacket::create(PacketType::AvatarQuery); + auto destinationBuffer = reinterpret_cast(avatarPacket->getPayload()); + unsigned char* bufferStart = destinationBuffer; - uint8_t numFrustums = (uint8_t)_conicalViews.size(); - memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums)); - destinationBuffer += sizeof(numFrustums); + uint8_t numFrustums = (uint8_t)_conicalViews.size(); + memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums)); + destinationBuffer += sizeof(numFrustums); - for (const auto& view : _conicalViews) { - destinationBuffer += view.serialize(destinationBuffer); + for (const auto& view : _conicalViews) { + destinationBuffer += view.serialize(destinationBuffer); + } + + avatarPacket->setPayloadSize(destinationBuffer - bufferStart); + + DependencyManager::get()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); } - - avatarPacket->setPayloadSize(destinationBuffer - bufferStart); - - DependencyManager::get()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); } @@ -6293,6 +6315,7 @@ void Application::clearDomainOctreeDetails() { qCDebug(interfaceapp) << "Clearing domain octree details..."; resetPhysicsReadyInformation(); + setIsInterstitialMode(true); _octreeServerSceneStats.withWriteLock([&] { _octreeServerSceneStats.clear(); @@ -6367,11 +6390,11 @@ void Application::nodeActivated(SharedNodePointer node) { _octreeQuery.incrementConnectionID(); } - if (node->getType() == NodeType::AudioMixer) { + if (node->getType() == NodeType::AudioMixer && !isInterstitialMode()) { DependencyManager::get()->negotiateAudioFormat(); } - if (node->getType() == NodeType::AvatarMixer) { + if (node->getType() == NodeType::AvatarMixer && !isInterstitialMode()) { _queryExpiry = SteadyClock::now(); // new avatar mixer, send off our identity packet on next update loop diff --git a/interface/src/Application.h b/interface/src/Application.h index c084c0033f..6bdfef78e1 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -328,6 +328,7 @@ signals: void activeDisplayPluginChanged(); void uploadRequest(QString path); + void interstitialModeChanged(bool interstitialMode); public slots: QVector pasteEntities(float x, float y, float z); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index fab512f787..0fcc253f53 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -121,7 +121,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) { quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; - if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS) { + if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !qApp->isInterstitialMode()) { // send head/hand data to the avatar mixer and voxel server PerformanceTimer perfTimer("send"); _myAvatar->sendAvatarDataPacket(); @@ -755,13 +755,13 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV QString currentSessionUUID = avatar->getSessionUUID().toString(); if (specificAvatarIdentifiers.isEmpty() || specificAvatarIdentifiers.contains(currentSessionUUID)) { QJsonObject thisAvatarPalData; - + auto myAvatar = DependencyManager::get()->getMyAvatar(); if (currentSessionUUID == myAvatar->getSessionUUID().toString()) { currentSessionUUID = ""; } - + thisAvatarPalData.insert("sessionUUID", currentSessionUUID); thisAvatarPalData.insert("sessionDisplayName", avatar->getSessionDisplayName()); thisAvatarPalData.insert("audioLoudness", avatar->getAudioLoudness()); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 98fbd8fea2..ec5ca903a0 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2212,6 +2212,7 @@ void MyAvatar::setHasScriptedBlendshapes(bool hasScriptedBlendshapes) { // send a forced avatarData update to make sure the script can send neutal blendshapes on unload // without having to wait for the update loop, make sure _hasScriptedBlendShapes is still true // before sending the update, or else it won't send the neutal blendshapes to the receiving clients + sendAvatarDataPacket(true); } _hasScriptedBlendShapes = hasScriptedBlendshapes; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 0eadcc5b66..a1487fa3ec 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -667,8 +667,7 @@ void AudioClient::stop() { } void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { - - /*char bitset; + char bitset; message->readPrimitive(&bitset); bool hasReverb = oneAtBit(bitset, HAS_REVERB_BIT); @@ -680,11 +679,10 @@ void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { - /* if (message->getType() == PacketType::SilentAudioFrame) { _silentInbound.increment(); } else { @@ -709,7 +707,7 @@ void AudioClient::handleAudioDataPacket(QSharedPointer message) // Audio output must exist and be correctly set up if we're going to process received audio _receivedAudioStream.parseData(*message); #endif -}*/ + } } AudioClient::Gate::Gate(AudioClient* audioClient) : @@ -1042,80 +1040,82 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { } void AudioClient::handleAudioInput(QByteArray& audioBuffer) { - if (_muted) { - _lastInputLoudness = 0.0f; - _timeSinceLastClip = 0.0f; - } else { - int16_t* samples = reinterpret_cast(audioBuffer.data()); - int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; - int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); - - if (_isNoiseGateEnabled) { - // The audio gate includes DC removal - _audioGate->render(samples, samples, numFrames); - } else { - _audioGate->removeDC(samples, samples, numFrames); - } - - int32_t loudness = 0; - assert(numSamples < 65536); // int32_t loudness cannot overflow - bool didClip = false; - for (int i = 0; i < numSamples; ++i) { - const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); - int32_t sample = std::abs((int32_t)samples[i]); - loudness += sample; - didClip |= (sample > CLIPPING_THRESHOLD); - } - _lastInputLoudness = (float)loudness / numSamples; - - if (didClip) { + if (!_interstitialMode) { + if (_muted) { + _lastInputLoudness = 0.0f; _timeSinceLastClip = 0.0f; - } else if (_timeSinceLastClip >= 0.0f) { - _timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE; + } else { + int16_t* samples = reinterpret_cast(audioBuffer.data()); + int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; + int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); + + if (_isNoiseGateEnabled) { + // The audio gate includes DC removal + _audioGate->render(samples, samples, numFrames); + } else { + _audioGate->removeDC(samples, samples, numFrames); + } + + int32_t loudness = 0; + assert(numSamples < 65536); // int32_t loudness cannot overflow + bool didClip = false; + for (int i = 0; i < numSamples; ++i) { + const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); + int32_t sample = std::abs((int32_t)samples[i]); + loudness += sample; + didClip |= (sample > CLIPPING_THRESHOLD); + } + _lastInputLoudness = (float)loudness / numSamples; + + if (didClip) { + _timeSinceLastClip = 0.0f; + } else if (_timeSinceLastClip >= 0.0f) { + _timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE; + } + + emit inputReceived(audioBuffer); } - emit inputReceived(audioBuffer); + emit inputLoudnessChanged(_lastInputLoudness); + + // state machine to detect gate opening and closing + bool audioGateOpen = (_lastInputLoudness != 0.0f); + bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened + bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed + _audioGateOpen = audioGateOpen; + + if (openedInLastBlock) { + emit noiseGateOpened(); + } else if (closedInLastBlock) { + emit noiseGateClosed(); + } + + // the codec must be flushed to silence before sending silent packets, + // so delay the transition to silent packets by one packet after becoming silent. + auto packetType = _shouldEchoToServer ? PacketType::MicrophoneAudioWithEcho : PacketType::MicrophoneAudioNoEcho; + if (!audioGateOpen && !closedInLastBlock) { + packetType = PacketType::SilentAudioFrame; + _silentOutbound.increment(); + } else { + _audioOutbound.increment(); + } + + Transform audioTransform; + audioTransform.setTranslation(_positionGetter()); + audioTransform.setRotation(_orientationGetter()); + + QByteArray encodedBuffer; + if (_encoder) { + _encoder->encode(audioBuffer, encodedBuffer); + } else { + encodedBuffer = audioBuffer; + } + + emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, + audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale, + packetType, _selectedCodecName); + _stats.sentPacket(); } - - emit inputLoudnessChanged(_lastInputLoudness); - - // state machine to detect gate opening and closing - bool audioGateOpen = (_lastInputLoudness != 0.0f); - bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened - bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed - _audioGateOpen = audioGateOpen; - - if (openedInLastBlock) { - emit noiseGateOpened(); - } else if (closedInLastBlock) { - emit noiseGateClosed(); - } - - // the codec must be flushed to silence before sending silent packets, - // so delay the transition to silent packets by one packet after becoming silent. - auto packetType = _shouldEchoToServer ? PacketType::MicrophoneAudioWithEcho : PacketType::MicrophoneAudioNoEcho; - if (!audioGateOpen && !closedInLastBlock) { - packetType = PacketType::SilentAudioFrame; - _silentOutbound.increment(); - } else { - _audioOutbound.increment(); - } - - Transform audioTransform; - audioTransform.setTranslation(_positionGetter()); - audioTransform.setRotation(_orientationGetter()); - - QByteArray encodedBuffer; - if (_encoder) { - _encoder->encode(audioBuffer, encodedBuffer); - } else { - encodedBuffer = audioBuffer; - } - - emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, - audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale, - packetType, _selectedCodecName); - _stats.sentPacket(); } void AudioClient::handleMicAudioInput() { @@ -2017,7 +2017,7 @@ void AudioClient::loadSettings() { _receivedAudioStream.setDynamicJitterBufferEnabled(dynamicJitterBufferEnabled.get()); _receivedAudioStream.setStaticJitterBufferFrames(staticJitterBufferFrames.get()); - qCDebug(audioclient) << "---- Initializing Audio Client ----"; + auto codecPlugins = PluginManager::getInstance()->getCodecPlugins(); for (auto& plugin : codecPlugins) { qCDebug(audioclient) << "Codec available:" << plugin->getName(); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 9ee7bcfeba..b665f85a13 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -188,6 +188,7 @@ public slots: void handleRecordedAudioInput(const QByteArray& audio); void reset(); void audioMixerKilled(); + void setInterstitialStatus(bool interstitialMode) { _interstitialMode = interstitialMode; } void setMuted(bool muted, bool emitSignal = true); bool isMuted() { return _muted; } @@ -417,6 +418,7 @@ private: QVector _activeLocalAudioInjectors; bool _isPlayingBackRecording { false }; + bool _interstitialMode { true }; CodecPluginPointer _codec; QString _selectedCodecName; From b9cfbfb3beae4d3ed0070d91e1bdac10c540a832 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 3 Aug 2018 09:59:21 -0700 Subject: [PATCH 020/744] adding interstitial page file to default sctipts --- interface/src/Application.cpp | 4 +- libraries/audio-client/src/AudioClient.cpp | 2 +- scripts/defaultScripts.js | 3 +- scripts/system/interstitialPage.js | 414 +++++++++++++++++++++ 4 files changed, 419 insertions(+), 4 deletions(-) create mode 100644 scripts/system/interstitialPage.js diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bbce49b941..bbe107e69e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3713,7 +3713,7 @@ void Application::keyPressEvent(QKeyEvent* event) { _controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface->isKeyCaptured(event)) { + if (_controllerScriptingInterface->isKeyCaptured(event) || isInterstitialMode()) { return; } @@ -5603,7 +5603,7 @@ void Application::update(float deltaTime) { // Transfer the user inputs to the driveKeys // FIXME can we drop drive keys and just have the avatar read the action states directly? myAvatar->clearDriveKeys(); - if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) { + if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT && !isInterstitialMode()) { if (!_controllerScriptingInterface->areActionsCaptured() && _myCamera.getMode() != CAMERA_MODE_MIRROR) { myAvatar->setDriveKey(MyAvatar::TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z)); myAvatar->setDriveKey(MyAvatar::TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y)); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a1487fa3ec..380d978fb1 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -2017,7 +2017,7 @@ void AudioClient::loadSettings() { _receivedAudioStream.setDynamicJitterBufferEnabled(dynamicJitterBufferEnabled.get()); _receivedAudioStream.setStaticJitterBufferFrames(staticJitterBufferFrames.get()); - + qCDebug(audioclient) << "---- Initializing Audio Client ----"; auto codecPlugins = PluginManager::getInstance()->getCodecPlugins(); for (auto& plugin : codecPlugins) { qCDebug(audioclient) << "Codec available:" << plugin->getName(); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index b275660c0f..662120be34 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -31,7 +31,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/dialTone.js", "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", - "system/emote.js" + "system/emote.js", + "system/interstitialpage" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js new file mode 100644 index 0000000000..af80e94605 --- /dev/null +++ b/scripts/system/interstitialPage.js @@ -0,0 +1,414 @@ +// +// interstitialPage.js +// scripts/system +// +// Created by Dante Ruiz on 08/02/2018. +// Copyright 2012 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global Script, Controller, Overlays, Quat, MyAvatar, Entities, print, Vec3, AddressManager, Render, Window, Toolbars, + Camera, HMD*/ + +(function() { + var MAX_X_SIZE = 3; + var isVisible = true; + var defaultOffset = 1.5; + var hifi = "HighFidelity"; + var VOLUME = 0.4; + var tune = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/crystals_and_voices.wav"); + var sample = null; + var MAX_LEFT_MARGIN = 1.9; + var INNER_CIRCLE_WIDTH = 4.7; + var DESTINATION_CARD_Y_OFFSET = 2; + var DEFAULT_TONE_MAPPING_EXPOSURE = 0.0; + var MIN_TONE_MAPPING_EXPOSURE = -5.0; + var SYSTEM_TOOL_BAR = "com.highfidelity.interface.toolbar.system"; + var MAX_ELAPSED_TIME = 5 * 1000; // time in ms + function isInFirstPerson() { + return (Camera.mode === "first person"); + } + + var toolbar = Toolbars.getToolbar(SYSTEM_TOOL_BAR); + var renderViewTask = Render.getConfig("RenderMainView"); + + var domainHostnameMap = { + eschatology: "Seth Alves", + blue: "Sam Cake", + thepines: "Roxie", + "dev-mobile": "HighFidelity", + "dev-mobile-master": "HighFidelity", + portalarium: "Bijou", + porange: "Caitlyn", + rust: hifi, + start: hifi, + miimusic: "Madysyn", + codex: "FluffyJenkins", + zaru: hifi, + help: hifi, + therealoasis: "Caitlyn", + vrmacy: "budgiebeats", + niccage: "OneLisa", + impromedia: "GeorgeDeac", + nest: "budgiebeats", + gabworld: "LeeGab", + vrtv: "GeoorgeDeac", + burrow: "budgiebeats", + leftcoast: "Lurks", + lazybones: "LazybonesJurassic", + skyriver: "Chamberlain", + chapel: "www.livin.today", + "hi-studio": hifi, + luskan: "jyoum", + arcadiabay: "Aitolda", + chime: hifi, + standupnow: "diva", + avreng: "GeorgeDeac", + atlas: "rocklin_guy", + steamedhams: "Alan_", + banff: hifi, + operahouse: hifi, + bankofhighfidelity: hifi, + tutorial: "WadeWatts", + nightsky: hifi, + garageband: hifi, + painting: hifi, + windwaker: "bijou", + fumbleland: "Lpasca", + monolith: "Nik", + bijou: "bijou", + morty: "bijou", + "hifiqa-rc-bots": hifi, + fightnight: hifi, + spirited: "Alan_", + "desert-oasis": "ryan", + springfield: "Alan_", + hall: "ryan", + "national-park": "ryan", + vector: "Nik", + bodymart: hifi, + "medievil-village": "ryan", + "villains-lair": "ryan", + "island-breeze": "ryan", + "classy-apartment": "ryan", + voxel: "FlameSoulis", + virtuoso: "noahglaseruc", + avatarisland: hifi, + ioab: "rocklin_guy", + tamait: "rocklin_guy", + konshulabs: "Konshu", + epic: "philip", + poopsburg: "Caitlyn", + east: hifi, + glitched: hifi, + calartsim: hifi, + calarts: hifi, + livin: "rocklin_guy", + fightclub: "philip", + thefactory: "whyroc", + wothal: "Alezia.Kurdis", + udacity: hifi, + json: "WadeWatts", + anonymous: "darlingnotin", + maker: hifi, + elisa: "elisahifi", + volxeltopia: hifi, + cupcake: hifi, + minigolf: hifi, + workshop: hifi, + vankh: "Alezia.Kurdis", + "the-crash-site": "WolfGang", + jjv360: "jjv3600", + distributed2: hifi, + anny: hifi, + university: hifi, + ludus: hifi, + stepford: "darlingnotin", + thespot: hifi + }; + + // Tips have a character limit of 69 + var userTips = [ + "Tip: Visit TheSpot to explore featured domains!", + "Tip: Visit our docs online to learn more about scripting!", + "Tip: Don't want others invading your personal space? Turn on the Bubble!", + "Tip: Want to make a friend? Shake hands with them in VR!", + "Tip: Enjoy live music? Visit Rust to dance your heart out!", + "Tip: Have you visited BodyMart to check out the new avatars recently?", + "Tip: Use the Create app to import models and create custom entities.", + "Tip: We're open source! Feel free to contribute to our code on GitHub!", + "Tip: What emotes have you used in the Emote app?", + "Tip: Take and share your snapshots with the everyone using the Snap app.", + "Tip: Did you know you can show websites in-world by creating a web entity?", + "Tip: Find out more information about domains by visiting our website!", + "Tip: Did you know you can get cool new apps from the Marketplace?", + "Tip: Print your snapshots from the Snap app to share with others!", + "Tip: Log in to make friends, visit new domains, and save avatars!" + ]; + + var loadingSphereID = Overlays.addOverlay("model", { + name: "Loading-Sphere", + position: Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0.0, y: -1.0, z: 0.0}), Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.95, z: 0})), + orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), + url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/black-sphere.fbx", + dimensions: { x: 20, y: 20, z: 20 }, + alpha: 1, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false + }); + + + var domainName = ""; + var domainNameTextID = Overlays.addOverlay("text3d", { + name: "Loading-Destination-Card-Text", + localPosition: { x: 0.0, y: DESTINATION_CARD_Y_OFFSET + 0.8, z: 5.45 }, + text: domainName, + textAlpha: 1, + backgroundAlpha: 0, + lineHeight: 0.42, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + parentID: loadingSphereID + }); + + var hostName = ""; + + var domainHostname = Overlays.addOverlay("text3d", { + name: "Loading-Hostname", + localPosition: { x: 0.0, y: DESTINATION_CARD_Y_OFFSET + 0.32, z: 5.45 }, + text: hostName, + textAlpha: 1, + backgroundAlpha: 0, + lineHeight: 0.13, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + parentID: loadingSphereID + }); + + var toolTip = ""; + + var domainToolTip = Overlays.addOverlay("text3d", { + name: "Loading-Tooltip", + localPosition: { x: 0.0 , y: DESTINATION_CARD_Y_OFFSET - 1.6, z: 5.45 }, + text: toolTip, + textAlpha: 1, + backgroundAlpha: 0, + lineHeight: 0.13, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + parentID: loadingSphereID + }); + + var loadingToTheSpotID = Overlays.addOverlay("image3d", { + name: "Loading-Destination-Card-Text", + localPosition: { x: 0.0 , y: DESTINATION_CARD_Y_OFFSET - 1.8, z: 5.45 }, + url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/goTo_button.png", + alpha: 1, + dimensions: { x: 1.2, y: 0.6}, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: loadingSphereID + }); + + var loadingBarPlacard = Overlays.addOverlay("image3d", { + name: "Loading-Bar-Placard", + localPosition: { x: 0.0 , y: DESTINATION_CARD_Y_OFFSET - 0.99, z: 5.45 }, + url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/loadingBar_placard.png", + alpha: 1, + dimensions: { x: 4, y: 2.8}, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: loadingSphereID + }); + + var loadingBarProgress = Overlays.addOverlay("image3d", { + name: "Loading-Bar-Progress", + localPosition: { x: 0.0 , y: DESTINATION_CARD_Y_OFFSET - 0.99, z: 5.45 }, + url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/loadingBar_progress.png", + alpha: 1, + dimensions: { x: 4, y: 2.8}, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: loadingSphereID + }); + + var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update + var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; + var timerset = false; + var lastInterval = Date.now(); + var timeElapsed = 0; + + + function getLeftMargin(overlayID, text) { + var textSize = Overlays.textSize(overlayID, text); + var sizeDifference = ((INNER_CIRCLE_WIDTH - textSize.width) / 2); + var leftMargin = -(MAX_LEFT_MARGIN - sizeDifference); + return leftMargin; + } + + + function domainChanged(domain) { + var name = AddressManager.placename; + domainName = name.charAt(0).toUpperCase() + name.slice(1); + var domainNameLeftMargin = getLeftMargin(domainNameTextID, domainName); + var textProperties = { + text: domainName, + leftMargin: domainNameLeftMargin + }; + + var BY = "by "; + var host = domainHostnameMap[location.placename]; + var text = BY + host; + var hostLeftMargin = getLeftMargin(domainHostname, text); + var hostnameProperties = { + text: BY + host, + leftMargin: hostLeftMargin + }; + + var randomIndex = Math.floor(Math.random() * userTips.length); + var tip = userTips[randomIndex]; + var tipLeftMargin = getLeftMargin(domainToolTip, tip); + var toolTipProperties = { + text: tip, + leftMargin: tipLeftMargin + }; + + var myAvatarDirection = Vec3.UNIT_NEG_Z; + var cardDirectionPrime = {x: 0 , y: 0, z: 5.5}; + var rotationDelta = Quat.rotationBetween(cardDirectionPrime, myAvatarDirection); + var overlayRotation = Quat.multiply(MyAvatar.orientation, rotationDelta); + var mainSphereProperties = { + orientation: overlayRotation + }; + + Overlays.editOverlay(loadingSphereID, mainSphereProperties); + Overlays.editOverlay(domainNameTextID, textProperties); + Overlays.editOverlay(domainHostname, hostnameProperties); + Overlays.editOverlay(domainToolTip, toolTipProperties); + } + + var THE_PLACE = "hifi://TheSpot"; + function clickedOnOverlay(overlayID, event) { + print(overlayID + " other: " + loadingToTheSpotID); + print(event.button === "Primary"); + if (loadingToTheSpotID === overlayID) { + if (timerset) { + timeElapsed = 0; + } + AddressManager.handleLookupString(THE_PLACE); + } + } + var previousCameraMode = Camera.mode; + var previousPhysicsStatus = 99999; + + function updateOverlays(physicsEnabled) { + var properties = { + visible: !physicsEnabled + }; + + var myAvatarDirection = Vec3.UNIT_NEG_Z; + var cardDirectionPrime = {x: 0 , y: 0, z: 5.5}; + var rotationDelta = Quat.rotationBetween(cardDirectionPrime, myAvatarDirection); + var overlayRotation = Quat.multiply(MyAvatar.orientation, rotationDelta); + var mainSphereProperties = { + visible: !physicsEnabled, + orientation: overlayRotation + }; + + if (!HMD.active) { + toolbar.writeProperty("visible", physicsEnabled); + MyAvatar.headOrientation = Quat.multiply(Quat.cancelOutRollAndPitch(MyAvatar.headOrientation), Quat.fromPitchYawRollDegrees(2.5, 0, 0)); + } + + renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = physicsEnabled; + renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = physicsEnabled; + renderViewTask.getConfig("LightingModel")["enablePointLight"] = physicsEnabled; + Overlays.editOverlay(loadingSphereID, mainSphereProperties); + Overlays.editOverlay(loadingToTheSpotID, properties); + Overlays.editOverlay(domainNameTextID, properties); + Overlays.editOverlay(domainHostname, properties); + Overlays.editOverlay(domainToolTip, properties); + Overlays.editOverlay(loadingBarPlacard, properties); + Overlays.editOverlay(loadingBarProgress, properties); + } + + function update() { + var physicsEnabled = Window.isPhysicsEnabled(); + var thisInterval = Date.now(); + var deltaTime = (thisInterval - lastInterval); + lastInterval = thisInterval; + if (physicsEnabled !== previousPhysicsStatus) { + if (!physicsEnabled && !timerset) { + updateOverlays(physicsEnabled); + sample = Audio.playSound(tune, { + localOnly: true, + position: MyAvatar.headPosition, + volume: VOLUME + }); + timeElapsed = 0; + timerset = true; + } + previousPhysicsStatus = physicsEnabled; + } + + if (timerset) { + timeElapsed += deltaTime; + if (timeElapsed >= MAX_ELAPSED_TIME) { + updateOverlays(true); + sample.stop(); + sample = null; + timerset = false; + } + + } + + Overlays.editOverlay(loadingSphereID, { + position: Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0.0, y: -1.7, z: 0.0}), Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.95, z: 0})) + }); + Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); + } + + Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); + Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); + Window.domainChanged.connect(domainChanged); + + function cleanup() { + Overlays.deleteOverlay(loadingSphereID); + Overlays.deleteOverlay(loadingToTheSpotID); + Overlays.deleteOverlay(domainNameTextID); + Overlays.deleteOverlay(domainHostname); + Overlays.deleteOverlay(domainToolTip); + Overlays.deleteOverlay(loadingBarPlacard); + Overlays.deleteOverlay(loadingBarProgress); + try { + } catch (e) { + } + } + + Script.scriptEnding.connect(cleanup); +}()); From a2717e2efa9af8405b496b5a91cf653dacdd62ac Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 3 Aug 2018 10:47:20 -0700 Subject: [PATCH 021/744] editing interstitialPage.js --- scripts/defaultScripts.js | 2 +- scripts/system/interstitialPage.js | 111 +---------------------------- 2 files changed, 4 insertions(+), 109 deletions(-) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 662120be34..a11ec98c40 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/emote.js", - "system/interstitialpage" + "system/interstitialPage.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index af80e94605..f90256b964 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -16,119 +16,18 @@ var MAX_X_SIZE = 3; var isVisible = true; var defaultOffset = 1.5; - var hifi = "HighFidelity"; var VOLUME = 0.4; var tune = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/crystals_and_voices.wav"); var sample = null; var MAX_LEFT_MARGIN = 1.9; var INNER_CIRCLE_WIDTH = 4.7; var DESTINATION_CARD_Y_OFFSET = 2; - var DEFAULT_TONE_MAPPING_EXPOSURE = 0.0; - var MIN_TONE_MAPPING_EXPOSURE = -5.0; var SYSTEM_TOOL_BAR = "com.highfidelity.interface.toolbar.system"; var MAX_ELAPSED_TIME = 5 * 1000; // time in ms - function isInFirstPerson() { - return (Camera.mode === "first person"); - } var toolbar = Toolbars.getToolbar(SYSTEM_TOOL_BAR); var renderViewTask = Render.getConfig("RenderMainView"); - var domainHostnameMap = { - eschatology: "Seth Alves", - blue: "Sam Cake", - thepines: "Roxie", - "dev-mobile": "HighFidelity", - "dev-mobile-master": "HighFidelity", - portalarium: "Bijou", - porange: "Caitlyn", - rust: hifi, - start: hifi, - miimusic: "Madysyn", - codex: "FluffyJenkins", - zaru: hifi, - help: hifi, - therealoasis: "Caitlyn", - vrmacy: "budgiebeats", - niccage: "OneLisa", - impromedia: "GeorgeDeac", - nest: "budgiebeats", - gabworld: "LeeGab", - vrtv: "GeoorgeDeac", - burrow: "budgiebeats", - leftcoast: "Lurks", - lazybones: "LazybonesJurassic", - skyriver: "Chamberlain", - chapel: "www.livin.today", - "hi-studio": hifi, - luskan: "jyoum", - arcadiabay: "Aitolda", - chime: hifi, - standupnow: "diva", - avreng: "GeorgeDeac", - atlas: "rocklin_guy", - steamedhams: "Alan_", - banff: hifi, - operahouse: hifi, - bankofhighfidelity: hifi, - tutorial: "WadeWatts", - nightsky: hifi, - garageband: hifi, - painting: hifi, - windwaker: "bijou", - fumbleland: "Lpasca", - monolith: "Nik", - bijou: "bijou", - morty: "bijou", - "hifiqa-rc-bots": hifi, - fightnight: hifi, - spirited: "Alan_", - "desert-oasis": "ryan", - springfield: "Alan_", - hall: "ryan", - "national-park": "ryan", - vector: "Nik", - bodymart: hifi, - "medievil-village": "ryan", - "villains-lair": "ryan", - "island-breeze": "ryan", - "classy-apartment": "ryan", - voxel: "FlameSoulis", - virtuoso: "noahglaseruc", - avatarisland: hifi, - ioab: "rocklin_guy", - tamait: "rocklin_guy", - konshulabs: "Konshu", - epic: "philip", - poopsburg: "Caitlyn", - east: hifi, - glitched: hifi, - calartsim: hifi, - calarts: hifi, - livin: "rocklin_guy", - fightclub: "philip", - thefactory: "whyroc", - wothal: "Alezia.Kurdis", - udacity: hifi, - json: "WadeWatts", - anonymous: "darlingnotin", - maker: hifi, - elisa: "elisahifi", - volxeltopia: hifi, - cupcake: hifi, - minigolf: hifi, - workshop: hifi, - vankh: "Alezia.Kurdis", - "the-crash-site": "WolfGang", - jjv360: "jjv3600", - distributed2: hifi, - anny: hifi, - university: hifi, - ludus: hifi, - stepford: "darlingnotin", - thespot: hifi - }; - // Tips have a character limit of 69 var userTips = [ "Tip: Visit TheSpot to explore featured domains!", @@ -282,11 +181,10 @@ }; var BY = "by "; - var host = domainHostnameMap[location.placename]; - var text = BY + host; + var text = BY var hostLeftMargin = getLeftMargin(domainHostname, text); var hostnameProperties = { - text: BY + host, + text: BY, leftMargin: hostLeftMargin }; @@ -323,7 +221,7 @@ AddressManager.handleLookupString(THE_PLACE); } } - var previousCameraMode = Camera.mode; + var previousPhysicsStatus = 99999; function updateOverlays(physicsEnabled) { @@ -405,9 +303,6 @@ Overlays.deleteOverlay(domainToolTip); Overlays.deleteOverlay(loadingBarPlacard); Overlays.deleteOverlay(loadingBarProgress); - try { - } catch (e) { - } } Script.scriptEnding.connect(cleanup); From 237762620316fb48c7ea2ec779c0e9775ef6ac88 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 29 May 2018 13:22:20 -0700 Subject: [PATCH 022/744] exposing physics progress --- interface/src/Application.cpp | 5 +++++ interface/src/Application.h | 5 +++++ .../src/scripting/WindowScriptingInterface.cpp | 13 +++++++++++++ interface/src/scripting/WindowScriptingInterface.h | 4 ++++ 4 files changed, 27 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 12dbf823b3..496c133aeb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5301,6 +5301,7 @@ void Application::resetPhysicsReadyInformation() { _fullSceneCounterAtLastPhysicsCheck = 0; _nearbyEntitiesCountAtLastPhysicsCheck = 0; _nearbyEntitiesStabilityCount = 0; + _nearbyEntitiesReadyCount = 0; _physicsEnabled = false; } @@ -6546,6 +6547,7 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { _nearbyEntitiesCountAtLastPhysicsCheck = nearbyCount; const uint32_t MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT = 3; + uint32_t readyNearbyEntities = 0; if (_nearbyEntitiesStabilityCount >= MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT) { // We've seen the same number of nearby entities for several stats packets in a row. assume we've got all // the local entities. @@ -6555,8 +6557,11 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { HIFI_FCDEBUG(interfaceapp(), "Physics disabled until entity loads: " << entity->getID() << entity->getName()); // don't break here because we want all the relevant entities to start their downloads result = false; + } else { + readyNearbyEntities++; } } + _nearbyEntitiesReadyCount = readyNearbyEntities; return result; } return false; diff --git a/interface/src/Application.h b/interface/src/Application.h index 6bdfef78e1..88aea0379f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -224,6 +224,10 @@ public: void setHmdTabletBecomesToolbarSetting(bool value); bool getPreferStylusOverLaser() { return _preferStylusOverLaserSetting.get(); } void setPreferStylusOverLaser(bool value); + + uint32_t getEntitiesStabilityCount() { return _nearbyEntitiesStabilityCount; } + uint32_t getNearbyEntitiesReadyCount() { return _nearbyEntitiesReadyCount; } + uint32_t getNearbyEntitiesCount() { return _nearbyEntitiesCountAtLastPhysicsCheck; } // FIXME: Remove setting completely or make available through JavaScript API? //bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); } bool getPreferAvatarFingerOverStylus() { return false; } @@ -723,6 +727,7 @@ private: uint32_t _fullSceneCounterAtLastPhysicsCheck { 0 }; // _fullSceneReceivedCounter last time we checked physics ready uint32_t _nearbyEntitiesCountAtLastPhysicsCheck { 0 }; // how many in-range entities last time we checked physics ready uint32_t _nearbyEntitiesStabilityCount { 0 }; // how many times has _nearbyEntitiesCountAtLastPhysicsCheck been the same + uint32_t _nearbyEntitiesReadyCount { 0 }; quint64 _lastPhysicsCheckTime { usecTimestampNow() }; // when did we last check to see if physics was ready bool _keyboardDeviceHasFocus { true }; diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index ba86925581..965e183023 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -584,3 +584,16 @@ void WindowScriptingInterface::onMessageBoxSelected(int button) { _messageBoxes.remove(id); } } + + +int WindowScriptingInterface::getPhysicsNearbyEntitiesReadyCount() { + return qApp->getNearbyEntitiesReadyCount(); +} + +int WindowScriptingInterface::getPhysicsNearbyEntitiesStabilityCount() { + return qApp->getEntitiesStabilityCount(); +} + +int WindowScriptingInterface::getPhysicsNearbyEntitiesCount() { + return qApp->getNearbyEntitiesCount(); +} diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 77895e0e76..035dcae9d0 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -561,6 +561,10 @@ public slots: */ void closeMessageBox(int id); + int getPhysicsNearbyEntitiesReadyCount(); + int getPhysicsNearbyEntitiesStabilityCount(); + int getPhysicsNearbyEntitiesCount(); + private slots: void onWindowGeometryChanged(const QRect& geometry); void onMessageBoxSelected(int button); From 6a4a56cee38aa3dc14a0ac50ab72f7000a33055d Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 6 Aug 2018 17:42:21 -0700 Subject: [PATCH 023/744] imporving the interstitial page script --- interface/src/Application.cpp | 1 + .../scripting/WindowScriptingInterface.cpp | 3 + .../src/scripting/WindowScriptingInterface.h | 3 + interface/src/ui/Stats.cpp | 4 + scripts/defaultScripts.js | 4 +- scripts/system/interstitialPage.js | 169 ++++++++++-------- 6 files changed, 108 insertions(+), 76 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ac14e2d636..a594756a80 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3458,6 +3458,7 @@ bool Application::isInterstitialMode() const { void Application::setIsInterstitialMode(bool interstitialMode) { if (_interstitialMode != interstitialMode) { + qDebug() << "-------> interstitial mode changed: " << _interstitialMode << " ------------> "; _interstitialMode = interstitialMode; emit interstitialModeChanged(_interstitialMode); } diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 965e183023..68be55f88a 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -53,6 +53,9 @@ WindowScriptingInterface::WindowScriptingInterface() { }); connect(qApp->getWindow(), &MainWindow::windowGeometryChanged, this, &WindowScriptingInterface::onWindowGeometryChanged); + connect(qApp, &Application::interstitialModeChanged, [this] (bool interstitialStatus) { + emit interstitialStatusChanged(interstitialStatus); + }); } WindowScriptingInterface::~WindowScriptingInterface() { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 035dcae9d0..7b0b6435f7 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -746,6 +746,9 @@ signals: */ void geometryChanged(QRect geometry); + + void interstitialStatusChanged(bool intersititalMode); + private: QString getPreviousBrowseLocation() const; void setPreviousBrowseLocation(const QString& location); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 6bb615948c..991dee9980 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -105,6 +105,10 @@ extern std::atomic DECIMATED_TEXTURE_COUNT; extern std::atomic RECTIFIED_TEXTURE_COUNT; void Stats::updateStats(bool force) { + + if (qApp->isInterstitialMode()) { + return; + } QQuickItem* parent = parentItem(); if (!force) { if (!Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index a11ec98c40..31510831c8 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -31,11 +31,11 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/dialTone.js", "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", - "system/emote.js", - "system/interstitialPage.js" + "system/emote.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", + "system/interstitialPage.js" //"system/chat.js" ]; diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index f90256b964..6960272e11 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -13,9 +13,11 @@ Camera, HMD*/ (function() { - var MAX_X_SIZE = 3; - var isVisible = true; - var defaultOffset = 1.5; + var MAX_X_SIZE = 5; + var EPSILON = 0.25; + var isVisible = false; + var STABILITY = 3.0; + var defaultOffset = -0.5; var VOLUME = 0.4; var tune = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/crystals_and_voices.wav"); var sample = null; @@ -50,14 +52,15 @@ var loadingSphereID = Overlays.addOverlay("model", { name: "Loading-Sphere", position: Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0.0, y: -1.0, z: 0.0}), Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.95, z: 0})), - orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), + orientation: Quat.multiply(Quat.fromVec3Degrees({x: 10, y: 180, z: 0}), MyAvatar.orientation), url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/black-sphere.fbx", dimensions: { x: 20, y: 20, z: 20 }, alpha: 1, visible: isVisible, ignoreRayIntersection: true, drawInFront: true, - grabbable: false + grabbable: false, + parentID: MyAvatar.SELF_ID }); @@ -128,7 +131,7 @@ var loadingBarPlacard = Overlays.addOverlay("image3d", { name: "Loading-Bar-Placard", - localPosition: { x: 0.0 , y: DESTINATION_CARD_Y_OFFSET - 0.99, z: 5.45 }, + localPosition: { x: 0.0 , y: DESTINATION_CARD_Y_OFFSET - 0.99, z: 5.52 }, url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/loadingBar_placard.png", alpha: 1, dimensions: { x: 4, y: 2.8}, @@ -161,6 +164,9 @@ var timerset = false; var lastInterval = Date.now(); var timeElapsed = 0; + var currentDomain = ""; + var timer = null; + var target = 0; function getLeftMargin(overlayID, text) { @@ -171,43 +177,58 @@ } + function resetValues() { + } + + function lerp(a, b, t) { + return ((1 - t) * a + t * b); + } + + function startInterstitialPage() { + if (timer === null) { + print("--------> start page <--------"); + updateOverlays(Window.isPhysicsEnabled()); + target = 0; + currentProgress = 0.1; + timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); + } + } + function domainChanged(domain) { - var name = AddressManager.placename; - domainName = name.charAt(0).toUpperCase() + name.slice(1); - var domainNameLeftMargin = getLeftMargin(domainNameTextID, domainName); - var textProperties = { - text: domainName, - leftMargin: domainNameLeftMargin - }; + if (domain !== currentDomain) { + print("----------> domain changed <-------------->"); + var name = AddressManager.placename; + domainName = name.charAt(0).toUpperCase() + name.slice(1); + var domainNameLeftMargin = getLeftMargin(domainNameTextID, domainName); + var textProperties = { + text: domainName, + leftMargin: domainNameLeftMargin + }; - var BY = "by "; - var text = BY - var hostLeftMargin = getLeftMargin(domainHostname, text); - var hostnameProperties = { - text: BY, - leftMargin: hostLeftMargin - }; + var BY = "by "; + var text = BY + var hostLeftMargin = getLeftMargin(domainHostname, text); + var hostnameProperties = { + text: BY, + leftMargin: hostLeftMargin + }; - var randomIndex = Math.floor(Math.random() * userTips.length); - var tip = userTips[randomIndex]; - var tipLeftMargin = getLeftMargin(domainToolTip, tip); - var toolTipProperties = { - text: tip, - leftMargin: tipLeftMargin - }; + var randomIndex = Math.floor(Math.random() * userTips.length); + var tip = userTips[randomIndex]; + var tipLeftMargin = getLeftMargin(domainToolTip, tip); + var toolTipProperties = { + text: tip, + leftMargin: tipLeftMargin + }; - var myAvatarDirection = Vec3.UNIT_NEG_Z; - var cardDirectionPrime = {x: 0 , y: 0, z: 5.5}; - var rotationDelta = Quat.rotationBetween(cardDirectionPrime, myAvatarDirection); - var overlayRotation = Quat.multiply(MyAvatar.orientation, rotationDelta); - var mainSphereProperties = { - orientation: overlayRotation - }; + Overlays.editOverlay(domainNameTextID, textProperties); + Overlays.editOverlay(domainHostname, hostnameProperties); + Overlays.editOverlay(domainToolTip, toolTipProperties); - Overlays.editOverlay(loadingSphereID, mainSphereProperties); - Overlays.editOverlay(domainNameTextID, textProperties); - Overlays.editOverlay(domainHostname, hostnameProperties); - Overlays.editOverlay(domainToolTip, toolTipProperties); + + startInterstitialPage(); + currentDomain = domain; + } } var THE_PLACE = "hifi://TheSpot"; @@ -222,26 +243,18 @@ } } - var previousPhysicsStatus = 99999; + var currentProgress = 0.1; function updateOverlays(physicsEnabled) { var properties = { visible: !physicsEnabled }; - var myAvatarDirection = Vec3.UNIT_NEG_Z; - var cardDirectionPrime = {x: 0 , y: 0, z: 5.5}; - var rotationDelta = Quat.rotationBetween(cardDirectionPrime, myAvatarDirection); - var overlayRotation = Quat.multiply(MyAvatar.orientation, rotationDelta); var mainSphereProperties = { - visible: !physicsEnabled, - orientation: overlayRotation + visible: !physicsEnabled }; - if (!HMD.active) { - toolbar.writeProperty("visible", physicsEnabled); - MyAvatar.headOrientation = Quat.multiply(Quat.cancelOutRollAndPitch(MyAvatar.headOrientation), Quat.fromPitchYawRollDegrees(2.5, 0, 0)); - } + Menu.setIsOptionChecked("Show Overlays", physicsEnabled); renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = physicsEnabled; renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = physicsEnabled; @@ -260,38 +273,46 @@ var thisInterval = Date.now(); var deltaTime = (thisInterval - lastInterval); lastInterval = thisInterval; - if (physicsEnabled !== previousPhysicsStatus) { - if (!physicsEnabled && !timerset) { - updateOverlays(physicsEnabled); - sample = Audio.playSound(tune, { - localOnly: true, - position: MyAvatar.headPosition, - volume: VOLUME - }); - timeElapsed = 0; - timerset = true; - } - previousPhysicsStatus = physicsEnabled; + timeElapsed += deltaTime; + + var nearbyEntitiesReadyCount = Window.getPhysicsNearbyEntitiesReadyCount(); + var stabilityCount = Window.getPhysicsNearbyEntitiesStabilityCount(); + var nearbyEntitiesCount = Window.getPhysicsNearbyEntitiesCount(); + + var stabilityPercentage = (stabilityCount / STABILITY); + if (stabilityPercentage > 1) { + stabilityPercentage = 1; } - if (timerset) { - timeElapsed += deltaTime; - if (timeElapsed >= MAX_ELAPSED_TIME) { - updateOverlays(true); - sample.stop(); - sample = null; - timerset = false; - } + var stabilityProgress = (MAX_X_SIZE * 0.75) * stabilityPercentage; + var entitiesLoadedPercentage = 1; + if (nearbyEntitiesCount > 0) { + entitiesLoadedPercentage = nearbyEntitiesReadyCount / nearbyEntitiesCount; } + var entitiesLoadedProgress = (MAX_X_SIZE * 0.25) * entitiesLoadedPercentage; + var progress = stabilityProgress + entitiesLoadedProgress; + if (progress >= target) { + target = progress; + } + currentProgress = lerp(currentProgress, target, 0.2); + var properties = { + localPosition: { x: -(currentProgress / 2) + 2, y: DESTINATION_CARD_Y_OFFSET - 0.99, z: 5.45 }, + dimensions: { + x: currentProgress, + y: 2.8 + } + }; - Overlays.editOverlay(loadingSphereID, { - position: Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0.0, y: -1.7, z: 0.0}), Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.95, z: 0})) - }); - Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); + Overlays.editOverlay(loadingBarProgress, properties); + if (physicsEnabled && (currentProgress >= (MAX_X_SIZE - EPSILON))) { + updateOverlays(physicsEnabled); + timer = null; + return; + } + timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); } - Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); Window.domainChanged.connect(domainChanged); From a9fd034f09ca9e8b8f024d188549f92cd9326f53 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 7 Aug 2018 16:02:54 -0700 Subject: [PATCH 024/744] switching to location api --- interface/src/Application.cpp | 4 ++-- scripts/system/interstitialPage.js | 26 ++++++++++++-------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a594756a80..39c24d7562 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1640,7 +1640,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo audioClient->setMuted(!audioClient->isMuted()); } else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) { cycleCamera(); - } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { + } else if (action == controller::toInt(controller::Action::CONTEXT_MENU) && !isInterstitialMode()) { toggleTabletUI(); } else if (action == controller::toInt(controller::Action::RETICLE_X)) { auto oldPos = getApplicationCompositor().getReticlePosition(); @@ -7806,7 +7806,7 @@ float Application::getRenderResolutionScale() const { } void Application::notifyPacketVersionMismatch() { - if (!_notifiedPacketVersionMismatchThisDomain) { + if (!_notifiedPacketVersionMismatchThisDomain && !isInterstitialMode()) { _notifiedPacketVersionMismatchThisDomain = true; QString message = "The location you are visiting is running an incompatible server version.\n"; diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 6960272e11..b18d88d796 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -186,7 +186,6 @@ function startInterstitialPage() { if (timer === null) { - print("--------> start page <--------"); updateOverlays(Window.isPhysicsEnabled()); target = 0; currentProgress = 0.1; @@ -195,8 +194,9 @@ } function domainChanged(domain) { + print("domain changed: " + domain); if (domain !== currentDomain) { - print("----------> domain changed <-------------->"); + MyAvatar.restoreAnimation(); var name = AddressManager.placename; domainName = name.charAt(0).toUpperCase() + name.slice(1); var domainNameLeftMargin = getLeftMargin(domainNameTextID, domainName); @@ -205,13 +205,11 @@ leftMargin: domainNameLeftMargin }; - var BY = "by "; - var text = BY - var hostLeftMargin = getLeftMargin(domainHostname, text); + /*var hostLeftMargin = getLeftMargin(domainHostname, text); var hostnameProperties = { text: BY, leftMargin: hostLeftMargin - }; + };*/ var randomIndex = Math.floor(Math.random() * userTips.length); var tip = userTips[randomIndex]; @@ -222,7 +220,7 @@ }; Overlays.editOverlay(domainNameTextID, textProperties); - Overlays.editOverlay(domainHostname, hostnameProperties); + // Overlays.editOverlay(domainHostname, hostnameProperties); Overlays.editOverlay(domainToolTip, toolTipProperties); @@ -234,12 +232,9 @@ var THE_PLACE = "hifi://TheSpot"; function clickedOnOverlay(overlayID, event) { print(overlayID + " other: " + loadingToTheSpotID); - print(event.button === "Primary"); if (loadingToTheSpotID === overlayID) { - if (timerset) { - timeElapsed = 0; - } - AddressManager.handleLookupString(THE_PLACE); + print("-------> heading to theb spot <--------"); + location.handleLookupString(THE_PLACE); } } @@ -254,7 +249,7 @@ visible: !physicsEnabled }; - Menu.setIsOptionChecked("Show Overlays", physicsEnabled); + // Menu.setIsOptionChecked("Show Overlays", physicsEnabled); renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = physicsEnabled; renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = physicsEnabled; @@ -314,7 +309,10 @@ } Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); - Window.domainChanged.connect(domainChanged); + location.hostChanged.connect(domainChanged); + location.lookupResultsFinished.connect(function() { + print("connected: " + location.isConnected()); + }); function cleanup() { Overlays.deleteOverlay(loadingSphereID); From 0fed48bd0e7b8ce0411dc25f5944ed2d1a3d0d3d Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 8 Aug 2018 18:15:27 -0700 Subject: [PATCH 025/744] improving interstitial page --- scripts/system/interstitialPage.js | 199 ++++++++++++++++++++++++----- 1 file changed, 164 insertions(+), 35 deletions(-) diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index b18d88d796..76900bd77c 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -10,25 +10,30 @@ // /* global Script, Controller, Overlays, Quat, MyAvatar, Entities, print, Vec3, AddressManager, Render, Window, Toolbars, - Camera, HMD*/ + Camera, HMD, location, Account*/ (function() { + Script.include("/~/system/libraries/Xform.js"); + var DEBUG = true; var MAX_X_SIZE = 5; var EPSILON = 0.25; var isVisible = false; var STABILITY = 3.0; - var defaultOffset = -0.5; var VOLUME = 0.4; var tune = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/crystals_and_voices.wav"); var sample = null; var MAX_LEFT_MARGIN = 1.9; var INNER_CIRCLE_WIDTH = 4.7; var DESTINATION_CARD_Y_OFFSET = 2; - var SYSTEM_TOOL_BAR = "com.highfidelity.interface.toolbar.system"; - var MAX_ELAPSED_TIME = 5 * 1000; // time in ms + var DEFAULT_Z_OFFSET = 5.45; - var toolbar = Toolbars.getToolbar(SYSTEM_TOOL_BAR); var renderViewTask = Render.getConfig("RenderMainView"); + var request = Script.require('request').request; + var BUTTON_PROPERTIES = { + text: "Interstitial" + }; + var tablet = null; + var button = null; // Tips have a character limit of 69 var userTips = [ @@ -49,12 +54,14 @@ "Tip: Log in to make friends, visit new domains, and save avatars!" ]; + var DEFAULT_DIMENSIONS = { x: 20, y: 20, z: 20 }; + var loadingSphereID = Overlays.addOverlay("model", { name: "Loading-Sphere", position: Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0.0, y: -1.0, z: 0.0}), Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.95, z: 0})), - orientation: Quat.multiply(Quat.fromVec3Degrees({x: 10, y: 180, z: 0}), MyAvatar.orientation), + orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/black-sphere.fbx", - dimensions: { x: 20, y: 20, z: 20 }, + dimensions: DEFAULT_DIMENSIONS, alpha: 1, visible: isVisible, ignoreRayIntersection: true, @@ -63,11 +70,23 @@ parentID: MyAvatar.SELF_ID }); + var anchorOverlay = Overlays.addOverlay("cube", { + dimensions: {x: 0.2, y: 0.2, z: 0.2}, + visible: true, + grabbable: false, + ignoreRayIntersection: true, + localPosition: {x: 0.0, y: getAnchorLocalYOffset(), z: DEFAULT_Z_OFFSET }, + orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), + solid: true, + drawInFront: true, + parentID: MyAvatar.SELF_ID + }); - var domainName = ""; + + var domainName = "Test"; var domainNameTextID = Overlays.addOverlay("text3d", { name: "Loading-Destination-Card-Text", - localPosition: { x: 0.0, y: DESTINATION_CARD_Y_OFFSET + 0.8, z: 5.45 }, + localPosition: { x: 0.0, y: 0.8, z: 0.0 }, text: domainName, textAlpha: 1, backgroundAlpha: 0, @@ -77,15 +96,15 @@ drawInFront: true, grabbable: false, localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), - parentID: loadingSphereID + parentID: anchorOverlay }); - var hostName = ""; + var domainText = ""; - var domainHostname = Overlays.addOverlay("text3d", { + var domainDescription = Overlays.addOverlay("text3d", { name: "Loading-Hostname", - localPosition: { x: 0.0, y: DESTINATION_CARD_Y_OFFSET + 0.32, z: 5.45 }, - text: hostName, + localPosition: { x: 0.0, y: 0.32, z: 0.0 }, + text: domainText, textAlpha: 1, backgroundAlpha: 0, lineHeight: 0.13, @@ -94,14 +113,14 @@ drawInFront: true, grabbable: false, localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), - parentID: loadingSphereID + parentID: anchorOverlay }); var toolTip = ""; var domainToolTip = Overlays.addOverlay("text3d", { name: "Loading-Tooltip", - localPosition: { x: 0.0 , y: DESTINATION_CARD_Y_OFFSET - 1.6, z: 5.45 }, + localPosition: { x: 0.0 , y: -1.6, z: 0.0 }, text: toolTip, textAlpha: 1, backgroundAlpha: 0, @@ -111,12 +130,12 @@ drawInFront: true, grabbable: false, localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), - parentID: loadingSphereID + parentID: anchorOverlay }); var loadingToTheSpotID = Overlays.addOverlay("image3d", { name: "Loading-Destination-Card-Text", - localPosition: { x: 0.0 , y: DESTINATION_CARD_Y_OFFSET - 1.8, z: 5.45 }, + localPosition: { x: 0.0 , y: -1.8, z: 0.0 }, url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/goTo_button.png", alpha: 1, dimensions: { x: 1.2, y: 0.6}, @@ -126,12 +145,12 @@ drawInFront: true, grabbable: false, localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), - parentID: loadingSphereID + parentID: anchorOverlay }); var loadingBarPlacard = Overlays.addOverlay("image3d", { name: "Loading-Bar-Placard", - localPosition: { x: 0.0 , y: DESTINATION_CARD_Y_OFFSET - 0.99, z: 5.52 }, + localPosition: { x: 0.0 , y: -0.99, z: 0.07 }, url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/loadingBar_placard.png", alpha: 1, dimensions: { x: 4, y: 2.8}, @@ -141,12 +160,12 @@ drawInFront: true, grabbable: false, localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), - parentID: loadingSphereID + parentID: anchorOverlay }); var loadingBarProgress = Overlays.addOverlay("image3d", { name: "Loading-Bar-Progress", - localPosition: { x: 0.0 , y: DESTINATION_CARD_Y_OFFSET - 0.99, z: 5.45 }, + localPosition: { x: 0.0 , y: -0.99, z: 0.0 }, url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/loadingBar_progress.png", alpha: 1, dimensions: { x: 4, y: 2.8}, @@ -156,7 +175,7 @@ drawInFront: true, grabbable: false, localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), - parentID: loadingSphereID + parentID: anchorOverlay }); var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update @@ -169,6 +188,16 @@ var target = 0; + function getAnchorLocalYOffset() { + var loadingSpherePosition = Overlays.getProperty(loadingSphereID, "position"); + var loadingSphereOrientation = Overlays.getProperty(loadingSphereID, "rotation"); + var overlayXform = new Xform(loadingSphereOrientation, loadingSpherePosition); + var worldToOverlayXform = overlayXform.inv(); + var headPosition = MyAvatar.getHeadPosition(); + var headPositionInOverlaySpace = worldToOverlayXform.xformPoint(headPosition); + return headPositionInOverlaySpace.y; + } + function getLeftMargin(overlayID, text) { var textSize = Overlays.textSize(overlayID, text); var sizeDifference = ((INNER_CIRCLE_WIDTH - textSize.width) / 2); @@ -176,7 +205,6 @@ return leftMargin; } - function resetValues() { } @@ -186,13 +214,28 @@ function startInterstitialPage() { if (timer === null) { + print("----------> starting <----------"); updateOverlays(Window.isPhysicsEnabled()); + startAudio(); target = 0; currentProgress = 0.1; timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); } } + function startAudio() { + sample = Audio.playSound(tune, { + localOnly: true, + position: MyAvatar.getHeadPosition(), + volume: VOLUME + }); + } + + function endAudio() { + sample.stop(); + sample = null; + } + function domainChanged(domain) { print("domain changed: " + domain); if (domain !== currentDomain) { @@ -205,11 +248,23 @@ leftMargin: domainNameLeftMargin }; - /*var hostLeftMargin = getLeftMargin(domainHostname, text); - var hostnameProperties = { - text: BY, - leftMargin: hostLeftMargin - };*/ + var url = Account.metaverseServerURL + '/api/v1/places/' + domain; + request({ + uri: url + }, function(error, data) { + if (data.status === "success") { + print("-----------> settings domain description <----------"); + var domainInfo = data.data; + var domainDescriptionText = domainInfo.place.description; + print("domainText: " + domainDescriptionText); + var leftMargin = getLeftMargin(domainDescription, domainDescriptionText); + var domainDescriptionProperties = { + text: domainDescriptionText, + leftMargin: leftMargin + }; + Overlays.editOverlay(domainDescription, domainDescriptionProperties); + } + }); var randomIndex = Math.floor(Math.random() * userTips.length); var tip = userTips[randomIndex]; @@ -220,7 +275,6 @@ }; Overlays.editOverlay(domainNameTextID, textProperties); - // Overlays.editOverlay(domainHostname, hostnameProperties); Overlays.editOverlay(domainToolTip, toolTipProperties); @@ -233,7 +287,6 @@ function clickedOnOverlay(overlayID, event) { print(overlayID + " other: " + loadingToTheSpotID); if (loadingToTheSpotID === overlayID) { - print("-------> heading to theb spot <--------"); location.handleLookupString(THE_PLACE); } } @@ -249,18 +302,70 @@ visible: !physicsEnabled }; + var domainTextProperties = { + text: domainText, + visible: !physicsEnabled + }; + // Menu.setIsOptionChecked("Show Overlays", physicsEnabled); + if (!HMD.active) { + MyAvatar.headOrientation = Quat.multiply(Quat.cancelOutRollAndPitch(MyAvatar.headOrientation), Quat.fromPitchYawRollDegrees(-3.0, 0, 0)); + } + renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = physicsEnabled; renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = physicsEnabled; renderViewTask.getConfig("LightingModel")["enablePointLight"] = physicsEnabled; Overlays.editOverlay(loadingSphereID, mainSphereProperties); Overlays.editOverlay(loadingToTheSpotID, properties); Overlays.editOverlay(domainNameTextID, properties); - Overlays.editOverlay(domainHostname, properties); + Overlays.editOverlay(domainDescription, domainTextProperties); Overlays.editOverlay(domainToolTip, properties); Overlays.editOverlay(loadingBarPlacard, properties); Overlays.editOverlay(loadingBarProgress, properties); + + Camera.mode = "first person"; + } + + + function scaleInterstitialPage(sensorToWorldScale) { + var yOffset = getAnchorLocalYOffset(); + var localPosition = { + x: 0.0, + y: yOffset, + z: 5.45 + }; + + Overlays.editOverlay(anchorOverlay, { localPosition: localPosition }); + } + + var progress = 0; + function updateProgress() { + print("updateProgress"); + var thisInterval = Date.now(); + var deltaTime = (thisInterval - lastInterval); + lastInterval = thisInterval; + timeElapsed += deltaTime; + + progress += MAX_X_SIZE * (deltaTime / 1000); + print(progress); + var properties = { + localPosition: { x: -(progress / 1.98) + 2, y: -0.99, z: 0.0 }, + dimensions: { + x: progress, + y: 2.8 + } + }; + + if (progress > MAX_X_SIZE) { + progress = 0; + } + + Overlays.editOverlay(loadingBarProgress, properties); + + if (!toggle) { + Script.setTimeout(updateProgress, BASIC_TIMER_INTERVAL_MS); + } } function update() { @@ -292,7 +397,7 @@ } currentProgress = lerp(currentProgress, target, 0.2); var properties = { - localPosition: { x: -(currentProgress / 2) + 2, y: DESTINATION_CARD_Y_OFFSET - 0.99, z: 5.45 }, + localPosition: { x: -(currentProgress / 2) + 2, y: 0.99, z: 5.45 }, dimensions: { x: currentProgress, y: 2.8 @@ -301,7 +406,9 @@ Overlays.editOverlay(loadingBarProgress, properties); if (physicsEnabled && (currentProgress >= (MAX_X_SIZE - EPSILON))) { + print("----------> ending <--------"); updateOverlays(physicsEnabled); + endAudio(); timer = null; return; } @@ -311,17 +418,39 @@ Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); location.hostChanged.connect(domainChanged); location.lookupResultsFinished.connect(function() { - print("connected: " + location.isConnected()); + print("connected: " + location.isConnected); }); + MyAvatar.sensorToWorldScaleChanged.connect(scaleInterstitialPage); + + var toggle = true; + if (DEBUG) { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton(BUTTON_PROPERTIES); + + button.clicked.connect(function() { + toggle = !toggle; + updateOverlays(toggle); + + if (!toggle) { + Script.setTimeout(updateProgress, BASIC_TIMER_INTERVAL_MS); + } + }); + } + function cleanup() { Overlays.deleteOverlay(loadingSphereID); Overlays.deleteOverlay(loadingToTheSpotID); Overlays.deleteOverlay(domainNameTextID); - Overlays.deleteOverlay(domainHostname); + Overlays.deleteOverlay(domainDescription); Overlays.deleteOverlay(domainToolTip); Overlays.deleteOverlay(loadingBarPlacard); Overlays.deleteOverlay(loadingBarProgress); + Overlays.deleteOverlay(anchorOverlay); + + if (DEBUG) { + tablet.removeButton(button); + } } Script.scriptEnding.connect(cleanup); From 521797b32f0e75a053fcda8d891b915eabff1379 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 9 Aug 2018 20:44:01 +1200 Subject: [PATCH 026/744] Script and state machine basics --- scripts/system/tabletRezzer.js | 237 +++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 scripts/system/tabletRezzer.js diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js new file mode 100644 index 0000000000..ebc2c2cbea --- /dev/null +++ b/scripts/system/tabletRezzer.js @@ -0,0 +1,237 @@ +// +// tabletRezzer.js +// +// Created by David Rowe on 9 Aug 2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global */ + +(function () { + + "use strict"; + + var // Overlay + proxyOverlay, + + // State machine + PROXY_HIDDEN = 0, + PROXY_VISIBLE = 1, + PROXY_GRABBED = 2, + PROXY_EXPANDING = 3, + TABLET_OPEN = 4, + STATE_STRINGS = ["PROXY_HIDDEN", "PROXY_VISIBLE", "PROXY_GRABBED", "PROXY_EXPANDING", "TABLET_OPEN"], + STATE_MACHINE, + rezzerState = PROXY_HIDDEN, + proxyHand, + + // Events + MIN_HAND_CAMERA_ANGLE = 30, + DEGREES_180 = 180, + MIN_HAND_CAMERA_ANGLE_COS = Math.cos(Math.PI * MIN_HAND_CAMERA_ANGLE / DEGREES_180), + updateTimer = null, + UPDATE_INTERVAL = 250, + + LEFT_HAND = 0, + RIGHT_HAND = 1, + DEBUG = true; + + // #region Utilities ======================================================================================================= + + function debug(message) { + if (!DEBUG) { + return; + } + print("DEBUG: " + message); + } + + function error(message) { + print("ERROR: " + message); + } + + function handJointName(hand) { + var jointName; + if (hand === LEFT_HAND) { + if (Camera.mode === "first person") { + jointName = "_CONTROLLER_LEFTHAND"; + } else if (Camera.mode === "third person") { + jointName = "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"; + } else { + jointName = "LeftHand"; + } + } else { + if (Camera.mode === "first person") { + jointName = "_CONTROLLER_RIGHTHAND"; + } else if (Camera.mode === "third person") { + jointName = "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"; + } else { + jointName = "RightHand"; + } + } + return jointName; + } + + function handJointIndex(hand) { + return MyAvatar.getJointIndex(handJointName(hand)); + } + + // #endregion + + // #region State Machine =================================================================================================== + + function enterProxyHidden() { + } + + function enterProxyVisible(hand) { + proxyHand = hand; + } + + STATE_MACHINE = { + PROXY_HIDDEN: { // Tablet proxy could be shown but isn't because hand is oriented to show it or aren't in HMD mode. + enter: enterProxyHidden, + update: null, + exit: null + }, + PROXY_VISIBLE: { // Tablet proxy is visible and attached to hand. + enter: enterProxyVisible, + update: null, + exit: null + }, + PROXY_GRABBED: { // Other hand has grabbed and is holding the tablet proxy. + enter: null, + update: null, + exit: null + }, + PROXY_EXPANDING: { // Tablet proxy has been released from grab and is expanding before showing tablet proper. + enter: null, + update: null, + exit: null + }, + TABLET_OPEN: { // Tablet proper is being displayed. + enter: null, + update: null, + exit: null + } + }; + + function setState(state, data) { + if (state !== rezzerState) { + debug("State transition from " + STATE_STRINGS[rezzerState] + " to " + STATE_STRINGS[state]); + if (STATE_MACHINE[STATE_STRINGS[rezzerState]].exit) { + STATE_MACHINE[STATE_STRINGS[rezzerState]].exit(data); + } + if (STATE_MACHINE[STATE_STRINGS[state]].enter) { + STATE_MACHINE[STATE_STRINGS[state]].enter(data); + } + rezzerState = state; + } else { + error("Null state transition: " + state + "!"); + } + } + + function updateState() { + STATE_MACHINE[STATE_STRINGS[rezzerState]].update(); + } + + // #endregion + + // #region Events ========================================================================================================== + + function shouldShowProxy(hand) { + // Should show tablet proxy if hand is oriented towards the camera. + var pose, + jointIndex, + handPosition, + handOrientation; + + pose = Controller.getPoseValue(hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand); + if (!pose.valid) { + return false; + } + + jointIndex = handJointIndex(hand); + handPosition = Vec3.sum(MyAvatar.position, + Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex))); + handOrientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex)); + + return Vec3.dot(Vec3.normalize(Vec3.subtract(handPosition, Camera.position)), Quat.getForward(handOrientation)) + > MIN_HAND_CAMERA_ANGLE_COS; + } + + function update() { + // Assumes that is HMD.mounted. + switch (rezzerState) { + case PROXY_HIDDEN: + // Compare palm directions of hands with vectors from palms to camera. + if (shouldShowProxy(LEFT_HAND)) { + setState(PROXY_VISIBLE, LEFT_HAND); + break; + } else if (shouldShowProxy(RIGHT_HAND)) { + setState(PROXY_VISIBLE, RIGHT_HAND); + break; + } + break; + case PROXY_VISIBLE: + // Check that palm direction of proxy hand still less than maximum angle. + if (!shouldShowProxy(proxyHand)) { + setState(PROXY_HIDDEN); + break; + } + break; + default: + error("Missing case: " + rezzerState); + } + + updateTimer = Script.setTimeout(update, UPDATE_INTERVAL); + } + + + function onMountedChanged() { + // Tablet proxy only available when HMD is mounted. + + if (HMD.mounted) { + if (updateTimer === null) { + update(); + } + return; + } + + if (updateTimer !== null) { + Script.clearTimeout(updateTimer); + updateTimer = null; + } + if (rezzerState !== PROXY_HIDDEN) { + setState(PROXY_HIDDEN); + } + } + + // #endregion + + // #region Start-up and tear-down ========================================================================================== + + function setUp() { + HMD.mountedChanged.connect(onMountedChanged); + HMD.displayModeChanged.connect(onMountedChanged); // For the case that the HMD is already worn when the script starts. + if (HMD.mounted) { + update(); + } + } + + function tearDown() { + HMD.displayModeChanged.disconnect(onMountedChanged); + HMD.mountedChanged.disconnect(onMountedChanged); + if (updateTimer !== null) { + Script.clearTimeout(updateTimer); + updateTimer = null; + } + } + + setUp(); + Script.scriptEnding.connect(tearDown); + + //#endregion + +}()); From 5bada416bb6570467a0343ad94493e732d3e3b5d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 10 Aug 2018 09:41:25 +1200 Subject: [PATCH 027/744] Show/hide/expand tablet proxy --- scripts/system/tabletRezzer.js | 136 +++++++++++++++++++++++++++++++-- 1 file changed, 129 insertions(+), 7 deletions(-) diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index ebc2c2cbea..a62b0741bc 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -8,14 +8,29 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global */ +/* global getTabletWidthFromSettings */ (function () { "use strict"; + Script.include("./libraries/utils.js"); + var // Overlay - proxyOverlay, + proxyOverlay = null, + TABLET_PROXY_DIMENSIONS = { x: 0.0638, y: 0.0965, z: 0.0045 }, + TABLET_PROXY_POSITION_LEFT_HAND = { + x: 0, + y: 0.07, // Distance from joint. + z: 0.07 // Distance above palm. + }, + TABLET_PROXY_POSITION_RIGHT_HAND = { + x: 0, + y: 0.07, // Distance from joint. + z: 0.07 // Distance above palm. + }, + TABLET_PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 90 }), + TABLET_PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: -90 }), // State machine PROXY_HIDDEN = 0, @@ -27,6 +42,12 @@ STATE_MACHINE, rezzerState = PROXY_HIDDEN, proxyHand, + PROXY_EXPAND_DURATION = 500, + PROXY_EXPAND_TIMEOUT = 25, + proxyExpandTimer = null, + proxyExpandStart, + proxyInitialWidth, + proxyTargetWidth, // Events MIN_HAND_CAMERA_ANGLE = 30, @@ -34,10 +55,11 @@ MIN_HAND_CAMERA_ANGLE_COS = Math.cos(Math.PI * MIN_HAND_CAMERA_ANGLE / DEGREES_180), updateTimer = null, UPDATE_INTERVAL = 250, + HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation", LEFT_HAND = 0, RIGHT_HAND = 1, - DEBUG = true; + DEBUG = false; // #region Utilities ======================================================================================================= @@ -83,10 +105,73 @@ // #region State Machine =================================================================================================== function enterProxyHidden() { + Overlays.deleteOverlay(proxyOverlay); + proxyOverlay = null; } function enterProxyVisible(hand) { proxyHand = hand; + proxyOverlay = Overlays.addOverlay("cube", { + parentID: MyAvatar.SELF_ID, + parentJointIndex: handJointIndex(proxyHand), + localPosition: proxyHand === LEFT_HAND ? TABLET_PROXY_POSITION_LEFT_HAND : TABLET_PROXY_POSITION_RIGHT_HAND, + localRotation: proxyHand === LEFT_HAND ? TABLET_PROXY_ROTATION_LEFT_HAND : TABLET_PROXY_ROTATION_RIGHT_HAND, + dimensions: TABLET_PROXY_DIMENSIONS, + solid: true, + grabbable: true, + displayInFront: true, + visible: true + }); + } + + function expandProxy() { + var scaleFactor = (Date.now() - proxyExpandStart) / PROXY_EXPAND_DURATION; + if (scaleFactor < 1) { + Overlays.editOverlay(proxyOverlay, { + dimensions: Vec3.multiply( + 1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth, + TABLET_PROXY_DIMENSIONS) + }); + proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); + return; + } + + proxyExpandTimer = null; + setState(TABLET_OPEN); + } + + function enterProxyExpanding() { + // Detach from hand. + Overlays.editOverlay(proxyOverlay, { + parentID: Uuid.NULL, + parentJointIndex: -1 + }); + + // Start expanding. + proxyInitialWidth = TABLET_PROXY_DIMENSIONS.x; + proxyTargetWidth = getTabletWidthFromSettings(); + proxyExpandStart = Date.now(); + proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); + } + + function exitProxyExpanding() { + if (proxyExpandTimer !== null) { + Script.clearTimeout(proxyExpandTimer); + proxyExpandTimer = null; + } + } + + function enterTabletOpen() { + var proxyOverlayProperties = Overlays.getProperties(proxyOverlay, ["position", "orientation"]); + + Overlays.deleteOverlay(proxyOverlay); + proxyOverlay = null; + + Overlays.editOverlay(HMD.tabletID, { + position: proxyOverlayProperties.position, + orientation: proxyOverlayProperties.orientation + }); + HMD.openTablet(true); } STATE_MACHINE = { @@ -106,12 +191,12 @@ exit: null }, PROXY_EXPANDING: { // Tablet proxy has been released from grab and is expanding before showing tablet proper. - enter: null, + enter: enterProxyExpanding, update: null, - exit: null + exit: exitProxyExpanding }, TABLET_OPEN: { // Tablet proper is being displayed. - enter: null, + enter: enterTabletOpen, update: null, exit: null } @@ -181,6 +266,14 @@ break; } break; + case PROXY_GRABBED: + case PROXY_EXPANDING: + break; + case TABLET_OPEN: + if (!HMD.showTablet) { + setState(PROXY_HIDDEN); + } + break; default: error("Missing case: " + rezzerState); } @@ -188,6 +281,24 @@ updateTimer = Script.setTimeout(update, UPDATE_INTERVAL); } + function onMessageReceived(channel, data, senderID, localOnly) { + var message; + + if (channel !== HIFI_OBJECT_MANIPULATION_CHANNEL) { + return; + } + + message = JSON.parse(data); + if (message.grabbedEntity !== proxyOverlay) { + return; + } + + if (message.action === "grab" && rezzerState === PROXY_VISIBLE) { + setState(PROXY_GRABBED); + } else if (message.action === "release" && rezzerState === PROXY_GRABBED) { + setState(PROXY_EXPANDING); + } + } function onMountedChanged() { // Tablet proxy only available when HMD is mounted. @@ -213,6 +324,9 @@ // #region Start-up and tear-down ========================================================================================== function setUp() { + Messages.subscribe(HIFI_OBJECT_MANIPULATION_CHANNEL); + Messages.messageReceived.connect(onMessageReceived); + HMD.mountedChanged.connect(onMountedChanged); HMD.displayModeChanged.connect(onMountedChanged); // For the case that the HMD is already worn when the script starts. if (HMD.mounted) { @@ -221,17 +335,25 @@ } function tearDown() { + setState(PROXY_HIDDEN); // Or just tear right down? Perhaps so. + + Messages.messageReceived.disconnect(onMessageReceived); + Messages.unsubscribe(HIFI_OBJECT_MANIPULATION_CHANNEL); + HMD.displayModeChanged.disconnect(onMountedChanged); HMD.mountedChanged.disconnect(onMountedChanged); if (updateTimer !== null) { Script.clearTimeout(updateTimer); updateTimer = null; } + if (proxyOverlay !== null) { + Overlays.deleteOverlay(proxyOverlay); + } } setUp(); Script.scriptEnding.connect(tearDown); - //#endregion + // #endregion }()); From 353121be7d374ae58061ac55db8106a60b0d163c Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 9 Aug 2018 16:29:37 -0700 Subject: [PATCH 028/744] making some changes --- interface/resources/images/loadingBar_v1.png | Bin 0 -> 20323 bytes scripts/system/interstitialPage.js | 38 ++++++++++--------- 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 interface/resources/images/loadingBar_v1.png diff --git a/interface/resources/images/loadingBar_v1.png b/interface/resources/images/loadingBar_v1.png new file mode 100644 index 0000000000000000000000000000000000000000..b5c77054c4b2e185da0992a2237d2eb80d9033fc GIT binary patch literal 20323 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 IQ8j<^ZyDs5-T(jq literal 0 HcmV?d00001 diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 76900bd77c..ae1f71d0dd 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -15,7 +15,7 @@ (function() { Script.include("/~/system/libraries/Xform.js"); var DEBUG = true; - var MAX_X_SIZE = 5; + var MAX_X_SIZE = 4; var EPSILON = 0.25; var isVisible = false; var STABILITY = 3.0; @@ -67,7 +67,6 @@ ignoreRayIntersection: true, drawInFront: true, grabbable: false, - parentID: MyAvatar.SELF_ID }); var anchorOverlay = Overlays.addOverlay("cube", { @@ -79,7 +78,7 @@ orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), solid: true, drawInFront: true, - parentID: MyAvatar.SELF_ID + parentID: loadingSphereID }); @@ -100,7 +99,6 @@ }); var domainText = ""; - var domainDescription = Overlays.addOverlay("text3d", { name: "Loading-Hostname", localPosition: { x: 0.0, y: 0.32, z: 0.0 }, @@ -150,7 +148,7 @@ var loadingBarPlacard = Overlays.addOverlay("image3d", { name: "Loading-Bar-Placard", - localPosition: { x: 0.0 , y: -0.99, z: 0.07 }, + localPosition: { x: 0.0, y: -0.99, z: 0.0 }, url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/loadingBar_placard.png", alpha: 1, dimensions: { x: 4, y: 2.8}, @@ -165,10 +163,10 @@ var loadingBarProgress = Overlays.addOverlay("image3d", { name: "Loading-Bar-Progress", - localPosition: { x: 0.0 , y: -0.99, z: 0.0 }, - url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/loadingBar_progress.png", + localPosition: { x: 0.0, y: -0.99, z: 0.0 }, + url: Script.resourcesPath() + "images/loadingBar_v1.png", alpha: 1, - dimensions: { x: 4, y: 2.8}, + dimensions: {x: 4, y: 2.8}, visible: isVisible, emissive: true, ignoreRayIntersection: false, @@ -187,6 +185,8 @@ var timer = null; var target = 0; + var connectionToDomainFailed = false; + function getAnchorLocalYOffset() { var loadingSpherePosition = Overlays.getProperty(loadingSphereID, "position"); @@ -219,6 +219,7 @@ startAudio(); target = 0; currentProgress = 0.1; + connectionToDomainFailed = false; timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); } } @@ -313,9 +314,9 @@ MyAvatar.headOrientation = Quat.multiply(Quat.cancelOutRollAndPitch(MyAvatar.headOrientation), Quat.fromPitchYawRollDegrees(-3.0, 0, 0)); } - renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = physicsEnabled; - renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = physicsEnabled; - renderViewTask.getConfig("LightingModel")["enablePointLight"] = physicsEnabled; + //renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = physicsEnabled; + //renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = physicsEnabled; + //renderViewTask.getConfig("LightingModel")["enablePointLight"] = physicsEnabled; Overlays.editOverlay(loadingSphereID, mainSphereProperties); Overlays.editOverlay(loadingToTheSpotID, properties); Overlays.editOverlay(domainNameTextID, properties); @@ -350,7 +351,7 @@ progress += MAX_X_SIZE * (deltaTime / 1000); print(progress); var properties = { - localPosition: { x: -(progress / 1.98) + 2, y: -0.99, z: 0.0 }, + localPosition: { x: -(progress / 2) + 2, y: -0.99, z: 0.0 }, dimensions: { x: progress, y: 2.8 @@ -403,11 +404,11 @@ y: 2.8 } }; - + print("progress: " + currentProgress); Overlays.editOverlay(loadingBarProgress, properties); - if (physicsEnabled && (currentProgress >= (MAX_X_SIZE - EPSILON))) { + if (((physicsEnabled && (currentProgress >= (MAX_X_SIZE - EPSILON))) || connectionToDomainFailed)) { print("----------> ending <--------"); - updateOverlays(physicsEnabled); + updateOverlays((physicsEnabled || connectionToDomainFailed)); endAudio(); timer = null; return; @@ -418,7 +419,10 @@ Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); location.hostChanged.connect(domainChanged); location.lookupResultsFinished.connect(function() { - print("connected: " + location.isConnected); + Script.setTimeout(function() { + print("location connected: " + location.isConnected); + connectionToDomainFailed = !location.isConnected; + }, 300); }); MyAvatar.sensorToWorldScaleChanged.connect(scaleInterstitialPage); @@ -433,7 +437,7 @@ updateOverlays(toggle); if (!toggle) { - Script.setTimeout(updateProgress, BASIC_TIMER_INTERVAL_MS); + //Script.setTimeout(updateProgress, BASIC_TIMER_INTERVAL_MS); } }); } From 0aa42d70d65ac47cbcef5973d55801bc546cd047 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 10 Aug 2018 12:37:21 +1200 Subject: [PATCH 029/744] Don't let the tablet proxy hand do the initial grab --- scripts/system/tabletRezzer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index a62b0741bc..e338e767bb 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -59,6 +59,7 @@ LEFT_HAND = 0, RIGHT_HAND = 1, + HAND_NAMES = ["LeftHand", "RightHand"], DEBUG = false; // #region Utilities ======================================================================================================= @@ -293,7 +294,9 @@ return; } - if (message.action === "grab" && rezzerState === PROXY_VISIBLE) { + // Don't transition into PROXY_GRABBED unless the tablet proxy is grabbed with "other" hand. However, once it has been + // grabbed then the original hand can grab it back and grow it from there. + if (message.action === "grab" && message.joint !== HAND_NAMES[proxyHand] && rezzerState === PROXY_VISIBLE) { setState(PROXY_GRABBED); } else if (message.action === "release" && rezzerState === PROXY_GRABBED) { setState(PROXY_EXPANDING); From b240a4248dfe7476fa869c25559ef323ea6eec2e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 10 Aug 2018 12:51:06 +1200 Subject: [PATCH 030/744] Size and position according to avatar scale --- scripts/system/tabletRezzer.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index e338e767bb..a34f79da11 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -56,6 +56,7 @@ updateTimer = null, UPDATE_INTERVAL = 250, HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation", + avatarScale = 1, LEFT_HAND = 0, RIGHT_HAND = 1, @@ -115,9 +116,10 @@ proxyOverlay = Overlays.addOverlay("cube", { parentID: MyAvatar.SELF_ID, parentJointIndex: handJointIndex(proxyHand), - localPosition: proxyHand === LEFT_HAND ? TABLET_PROXY_POSITION_LEFT_HAND : TABLET_PROXY_POSITION_RIGHT_HAND, + localPosition: Vec3.multiply(avatarScale, + proxyHand === LEFT_HAND ? TABLET_PROXY_POSITION_LEFT_HAND : TABLET_PROXY_POSITION_RIGHT_HAND), localRotation: proxyHand === LEFT_HAND ? TABLET_PROXY_ROTATION_LEFT_HAND : TABLET_PROXY_ROTATION_RIGHT_HAND, - dimensions: TABLET_PROXY_DIMENSIONS, + dimensions: Vec3.multiply(avatarScale, TABLET_PROXY_DIMENSIONS), solid: true, grabbable: true, displayInFront: true, @@ -130,7 +132,7 @@ if (scaleFactor < 1) { Overlays.editOverlay(proxyOverlay, { dimensions: Vec3.multiply( - 1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth, + avatarScale * (1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth), TABLET_PROXY_DIMENSIONS) }); proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); @@ -282,6 +284,12 @@ updateTimer = Script.setTimeout(update, UPDATE_INTERVAL); } + function onScaleChanged() { + avatarScale = MyAvatar.scale; + // Clamp scale in order to work around M17434. + avatarScale = Math.max(MyAvatar.getDomainMinScale(), Math.min(MyAvatar.getDomainMaxScale(), avatarScale)); + } + function onMessageReceived(channel, data, senderID, localOnly) { var message; @@ -327,6 +335,8 @@ // #region Start-up and tear-down ========================================================================================== function setUp() { + MyAvatar.scaleChanged.connect(onScaleChanged); + Messages.subscribe(HIFI_OBJECT_MANIPULATION_CHANNEL); Messages.messageReceived.connect(onMessageReceived); @@ -343,6 +353,8 @@ Messages.messageReceived.disconnect(onMessageReceived); Messages.unsubscribe(HIFI_OBJECT_MANIPULATION_CHANNEL); + MyAvatar.scaleChanged.disconnect(onScaleChanged); + HMD.displayModeChanged.disconnect(onMountedChanged); HMD.mountedChanged.disconnect(onMountedChanged); if (updateTimer !== null) { From 557f18f370e9ac2bef5715beb881af7680b830d2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 10 Aug 2018 13:10:24 +1200 Subject: [PATCH 031/744] Don't show proxy if tablet has been opened by other means --- scripts/system/tabletRezzer.js | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index a34f79da11..6efaa33ed6 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -107,8 +107,10 @@ // #region State Machine =================================================================================================== function enterProxyHidden() { - Overlays.deleteOverlay(proxyOverlay); - proxyOverlay = null; + if (proxyOverlay) { + Overlays.deleteOverlay(proxyOverlay); + proxyOverlay = null; + } } function enterProxyVisible(hand) { @@ -253,30 +255,39 @@ // Assumes that is HMD.mounted. switch (rezzerState) { case PROXY_HIDDEN: + // Don't show proxy is tablet is already displayed. + if (HMD.showTablet) { + break; + } // Compare palm directions of hands with vectors from palms to camera. if (shouldShowProxy(LEFT_HAND)) { setState(PROXY_VISIBLE, LEFT_HAND); - break; } else if (shouldShowProxy(RIGHT_HAND)) { setState(PROXY_VISIBLE, RIGHT_HAND); - break; } break; case PROXY_VISIBLE: + // Hide proxy if tablet has been displayed by other means. + if (HMD.showTablet) { + setState(PROXY_HIDDEN); + break; + } // Check that palm direction of proxy hand still less than maximum angle. if (!shouldShowProxy(proxyHand)) { setState(PROXY_HIDDEN); - break; } break; case PROXY_GRABBED: case PROXY_EXPANDING: - break; - case TABLET_OPEN: - if (!HMD.showTablet) { + // Hide proxy if tablet has been displayed by other means. + if (HMD.showTablet) { setState(PROXY_HIDDEN); } break; + case TABLET_OPEN: + // Immediately transition back to PROXY_HIDDEN. + setState(PROXY_HIDDEN); + break; default: error("Missing case: " + rezzerState); } From bc456ae5c8c010b6784b9e058454fd8ed0e5b14f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 10 Aug 2018 13:18:21 +1200 Subject: [PATCH 032/744] Don't show proxy if in toolbar mode --- scripts/system/tabletRezzer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index 6efaa33ed6..390eaadded 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -48,6 +48,7 @@ proxyExpandStart, proxyInitialWidth, proxyTargetWidth, + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"), // Events MIN_HAND_CAMERA_ANGLE = 30, @@ -255,8 +256,8 @@ // Assumes that is HMD.mounted. switch (rezzerState) { case PROXY_HIDDEN: - // Don't show proxy is tablet is already displayed. - if (HMD.showTablet) { + // Don't show proxy is tablet is already displayed or are in toolbar mode. + if (HMD.showTablet || tablet.toolbarMode) { break; } // Compare palm directions of hands with vectors from palms to camera. From 3732de51a37f98c844527959d8a28d4c630f1c9d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 10 Aug 2018 13:25:41 +1200 Subject: [PATCH 033/744] Tidying --- scripts/system/tabletRezzer.js | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index 390eaadded..a84c53ad9e 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -55,7 +55,7 @@ DEGREES_180 = 180, MIN_HAND_CAMERA_ANGLE_COS = Math.cos(Math.PI * MIN_HAND_CAMERA_ANGLE / DEGREES_180), updateTimer = null, - UPDATE_INTERVAL = 250, + UPDATE_INTERVAL = 300, HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation", avatarScale = 1, @@ -223,10 +223,6 @@ } } - function updateState() { - STATE_MACHINE[STATE_STRINGS[rezzerState]].update(); - } - // #endregion // #region Events ========================================================================================================== @@ -360,22 +356,20 @@ } function tearDown() { - setState(PROXY_HIDDEN); // Or just tear right down? Perhaps so. + if (updateTimer !== null) { + Script.clearTimeout(updateTimer); + updateTimer = null; + } + + setState(PROXY_HIDDEN); + + HMD.displayModeChanged.disconnect(onMountedChanged); + HMD.mountedChanged.disconnect(onMountedChanged); Messages.messageReceived.disconnect(onMessageReceived); Messages.unsubscribe(HIFI_OBJECT_MANIPULATION_CHANNEL); MyAvatar.scaleChanged.disconnect(onScaleChanged); - - HMD.displayModeChanged.disconnect(onMountedChanged); - HMD.mountedChanged.disconnect(onMountedChanged); - if (updateTimer !== null) { - Script.clearTimeout(updateTimer); - updateTimer = null; - } - if (proxyOverlay !== null) { - Overlays.deleteOverlay(proxyOverlay); - } } setUp(); From c89955b416a446ea0746bd7e135a85b133e5b10a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 10 Aug 2018 13:53:53 +1200 Subject: [PATCH 034/744] Use FBX for tablet proxy --- scripts/system/assets/models/tinyTablet.fbx | Bin 0 -> 42864 bytes scripts/system/tabletRezzer.js | 6 ++++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 scripts/system/assets/models/tinyTablet.fbx diff --git a/scripts/system/assets/models/tinyTablet.fbx b/scripts/system/assets/models/tinyTablet.fbx new file mode 100644 index 0000000000000000000000000000000000000000..104d2df8cdb57bdaf23310a3a82baa26f9e82d16 GIT binary patch literal 42864 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 literal 0 HcmV?d00001 diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index a84c53ad9e..792deec164 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -18,7 +18,8 @@ var // Overlay proxyOverlay = null, - TABLET_PROXY_DIMENSIONS = { x: 0.0638, y: 0.0965, z: 0.0045 }, + TABLET_PROXY_MODEL = Script.resolvePath("./assets/models/tinyTablet.fbx"), + TABLET_PROXY_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0046 }, // Proportional to tablet proper. TABLET_PROXY_POSITION_LEFT_HAND = { x: 0, y: 0.07, // Distance from joint. @@ -116,7 +117,8 @@ function enterProxyVisible(hand) { proxyHand = hand; - proxyOverlay = Overlays.addOverlay("cube", { + proxyOverlay = Overlays.addOverlay("model", { + url: TABLET_PROXY_MODEL, parentID: MyAvatar.SELF_ID, parentJointIndex: handJointIndex(proxyHand), localPosition: Vec3.multiply(avatarScale, From 46297c6b22aa4b1f2128ac4ef4acde155e1b4500 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 10 Aug 2018 10:20:39 -0700 Subject: [PATCH 035/744] interstitial page --- scripts/system/interstitialPage.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index ae1f71d0dd..149e1e141b 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -67,11 +67,12 @@ ignoreRayIntersection: true, drawInFront: true, grabbable: false, + parentID: MyAvatar.SELF_ID }); var anchorOverlay = Overlays.addOverlay("cube", { dimensions: {x: 0.2, y: 0.2, z: 0.2}, - visible: true, + visible: false, grabbable: false, ignoreRayIntersection: true, localPosition: {x: 0.0, y: getAnchorLocalYOffset(), z: DEFAULT_Z_OFFSET }, @@ -314,9 +315,9 @@ MyAvatar.headOrientation = Quat.multiply(Quat.cancelOutRollAndPitch(MyAvatar.headOrientation), Quat.fromPitchYawRollDegrees(-3.0, 0, 0)); } - //renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = physicsEnabled; - //renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = physicsEnabled; - //renderViewTask.getConfig("LightingModel")["enablePointLight"] = physicsEnabled; + renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = physicsEnabled; + renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = physicsEnabled; + renderViewTask.getConfig("LightingModel")["enablePointLight"] = physicsEnabled; Overlays.editOverlay(loadingSphereID, mainSphereProperties); Overlays.editOverlay(loadingToTheSpotID, properties); Overlays.editOverlay(domainNameTextID, properties); @@ -350,15 +351,18 @@ progress += MAX_X_SIZE * (deltaTime / 1000); print(progress); + if (progress > MAX_X_SIZE) { + progress = 4; + } var properties = { - localPosition: { x: -(progress / 2) + 2, y: -0.99, z: 0.0 }, + localPosition: { x: 2.0 - (progress / 2), y: -0.99, z: -0.3 }, dimensions: { x: progress, y: 2.8 } }; - if (progress > MAX_X_SIZE) { + if (progress >= MAX_X_SIZE) { progress = 0; } @@ -398,7 +402,7 @@ } currentProgress = lerp(currentProgress, target, 0.2); var properties = { - localPosition: { x: -(currentProgress / 2) + 2, y: 0.99, z: 5.45 }, + localPosition: { x: 2 - (currentProgress / 2), y: -0.99, z: -0.3 }, dimensions: { x: currentProgress, y: 2.8 From 7a7fca3982573f1bba648244e4dfe3982614e49e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 11 Aug 2018 05:34:01 +1200 Subject: [PATCH 036/744] Update tiny tablet model --- scripts/system/assets/models/tinyTablet.fbx | Bin 42864 -> 411712 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/scripts/system/assets/models/tinyTablet.fbx b/scripts/system/assets/models/tinyTablet.fbx index 104d2df8cdb57bdaf23310a3a82baa26f9e82d16..cdcdf8af3461946f9050f07f897312fb77f5ef38 100644 GIT binary patch literal 411712 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 Date: Sat, 11 Aug 2018 06:18:26 +1200 Subject: [PATCH 037/744] Reorient tablet proxy --- scripts/system/tabletRezzer.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index 792deec164..2b6e738a26 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -30,8 +30,14 @@ y: 0.07, // Distance from joint. z: 0.07 // Distance above palm. }, + /* + // Aligned cross-palm. TABLET_PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 90 }), TABLET_PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: -90 }), + */ + // Aligned with palm. + TABLET_PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + TABLET_PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), // State machine PROXY_HIDDEN = 0, From 38d8a9a2da11eff836bdc1d184e3b56a4b5a6bbb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 11 Aug 2018 07:00:50 +1200 Subject: [PATCH 038/744] Send messages locally if not connected to a domain --- libraries/networking/src/MessagesClient.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/networking/src/MessagesClient.cpp b/libraries/networking/src/MessagesClient.cpp index 2302c22a48..8f252ac69b 100644 --- a/libraries/networking/src/MessagesClient.cpp +++ b/libraries/networking/src/MessagesClient.cpp @@ -116,16 +116,16 @@ void MessagesClient::handleMessagesPacket(QSharedPointer receiv void MessagesClient::sendMessage(QString channel, QString message, bool localOnly) { auto nodeList = DependencyManager::get(); + QUuid senderID = nodeList->getSessionUUID(); if (localOnly) { - QUuid senderID = nodeList->getSessionUUID(); emit messageReceived(channel, message, senderID, true); } else { SharedNodePointer messagesMixer = nodeList->soloNodeOfType(NodeType::MessagesMixer); - if (messagesMixer) { - QUuid senderID = nodeList->getSessionUUID(); auto packetList = encodeMessagesPacket(channel, message, senderID); nodeList->sendPacketList(std::move(packetList), *messagesMixer); + } else { + emit messageReceived(channel, message, senderID, true); } } } From 2a177e04e165631145ae432775193ba08024c452 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 11 Aug 2018 10:11:56 +1200 Subject: [PATCH 039/744] Misc. tweaks --- libraries/networking/src/MessagesClient.cpp | 5 +++-- scripts/system/tabletRezzer.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/networking/src/MessagesClient.cpp b/libraries/networking/src/MessagesClient.cpp index 8f252ac69b..d6f9d041ea 100644 --- a/libraries/networking/src/MessagesClient.cpp +++ b/libraries/networking/src/MessagesClient.cpp @@ -132,16 +132,17 @@ void MessagesClient::sendMessage(QString channel, QString message, bool localOnl void MessagesClient::sendData(QString channel, QByteArray data, bool localOnly) { auto nodeList = DependencyManager::get(); + QUuid senderID = nodeList->getSessionUUID(); if (localOnly) { - QUuid senderID = nodeList->getSessionUUID(); emit dataReceived(channel, data, senderID, true); } else { SharedNodePointer messagesMixer = nodeList->soloNodeOfType(NodeType::MessagesMixer); - if (messagesMixer) { QUuid senderID = nodeList->getSessionUUID(); auto packetList = encodeMessagesDataPacket(channel, data, senderID); nodeList->sendPacketList(std::move(packetList), *messagesMixer); + } else { + emit dataReceived(channel, data, senderID, true); } } } diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index 2b6e738a26..b5f94a7c87 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -49,7 +49,7 @@ STATE_MACHINE, rezzerState = PROXY_HIDDEN, proxyHand, - PROXY_EXPAND_DURATION = 500, + PROXY_EXPAND_DURATION = 250, PROXY_EXPAND_TIMEOUT = 25, proxyExpandTimer = null, proxyExpandStart, From 75cc1c62c05b8c9e6b52d8b4166fda533c72bbe6 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 10 Aug 2018 16:53:34 -0700 Subject: [PATCH 040/744] mostly done interstitial page --- .../resources/images/loadingBar_placard.png | Bin 0 -> 2737 bytes .../resources/images/loadingBar_progress.png | Bin 0 -> 20396 bytes interface/resources/images/loadingBar_v1.png | Bin 20323 -> 0 bytes interface/src/Application.cpp | 11 ++++-- interface/src/Application.h | 2 +- interface/src/Menu.cpp | 1 + scripts/defaultScripts.js | 3 +- scripts/system/interstitialPage.js | 37 ++++++++++-------- 8 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 interface/resources/images/loadingBar_placard.png create mode 100644 interface/resources/images/loadingBar_progress.png delete mode 100644 interface/resources/images/loadingBar_v1.png diff --git a/interface/resources/images/loadingBar_placard.png b/interface/resources/images/loadingBar_placard.png new file mode 100644 index 0000000000000000000000000000000000000000..9fd4c9e71f433d71be2c220d29e30e938abc3a8c GIT binary patch literal 2737 zcmbVOYgAL&6+WQg$U_`OaI_9F3=RzD9TE}}KnWmKC}O6lh#|STNCxsC2_%3BJ`gFQ zR%Qy+QLtE_6{M_zDrkZV>KK8R0*VH;lqi)qPKb~|??td(9miR#`LXZ0=X~G(_St9e zbCVYt5$b5S!VUo77`A~E1;Bh{2x306MeM-J@iWNd6J_vL{Vf#`xfAIEX|gW9SS#iAH3QscSK0B8g5Q(g`FAj!0q> zX-oH=QMk`};J^w#>Dw)cW#QrP97`0li9%9FQ1fL35}rs9 zi_v|}pcTp}_&;I17p>r?$zVbhtdOS2A>=&b-RH_& zmMFtGY%H>Z7m9>TU#h^@hbExo1au*UgM?Hv&X)!WaXv(WFP{w4VKSdF=jVNVN)Ro? zcRhv52;y)^q+nkLk?P9{2_bQ4KEWKS4|$FoCQ&H)5(u8t79rZRT*?Qz%pf_;S4!nv zsWfS>3L+DvN~t12D#HW?VLYQn5}{P3@Ip`M?S?t9T$Bn6L*!C1W+uf<(R<_tQ~{aB zCsJ@!h)l;(NYr>7pXlR*4RA(Hwl!)aQ3KHX1O!^4*7JXc zKhJx=i=W_~)pykJ;wFs)m;E(ueI^{DMqHMDy}PwE<3;ny0vtGyiA*i`cdhlZp?zs} zto~{&DO9kislVXSC)GHEmkk82+IZ`g*;;*Gz}X=yZemE-3z#Ts>+2Y`#S zu!aO(UU6siqDtI9z2_l};$y7=FH;(G?~~36ns2Xd&)oBPBM8|EK5MYZbT3F5qr|6# zC-zeaa#PU9#{!}UTN-suJMQwTFW6<1>jPGeZPQgzb#%7N1fpNuM8@bV({?irLY?Jb zSHqK+YCqEkxQ&VXhgwP#yDH9sNHoJ&9__bm-xz!l~;>$0v&hIgE*zqDW=hBQvA2ZwB2I?ZkgcIFg!GqQE!fJdPI zOr>}A=w6)nc-Q3lFH@x%OSL5>CAX@|#?r0~rPjUr0yv{--6S98C0a%5Mn~oPSKT{z z?5Mx8+9vS8v}C@qRAB7LF`r$6ahg~kfI!>0wy(XdE)Mf33R2}-JRa{jW{HWlUff{0 zg*BgB=kGL~wy@RN^bH#6Cb_9)dhw}lGr@5>_z1oxZQJ5X{n8WVB%V|CLCO8`;p9)v zms)w@8VVSN5Iixj<9Pmp-oC%(o$e&|djP-G{)fx#O_!?$?L2c^j?2+DddE=iOdXu( z86%`qMW?2CtO{dcPfn4Gi%Ug?%2C`5)GCi!I&X}pn#fvVI*FpS)R#qjJ4-I*JB1$H z7F65#kEiGT#H%$%O}c;9jP5{A;5D9jfqqNn-5v-BZr)foZK%YfNdy+kSa!VysglIR z{NNkJgv~e6EI8n;j{8{xUhiP`W-WTi(2a`}w%VFKDpQNA1F}pl0x6S@S0^7OyMjzt zJs15(gPxW)vBX+kyZu<*8^p7=+{@;x=4;a#f3gWQ1wC7U^yP?)_4A3p*k|)5)}o9A zH{&ZaYtZ`HaUQY?4(rEz@SwTz_2~X~4IcG)u=7}D2K=cT&r0^H2O?|yNZf|={UpRu$ zQWs2L8!k2U$^CeSfsB<%Qx@DF*!tu|)%B=Vmt109CoJQrr?a?Wr|<7H`e;1_s-WSVbhk?t=vUl2W$I^3r9?Q(hp{? zEWb7%b23YidR!Lrd_5^TnBBx#tl$<-5>S1VuFNUdl-C+iY&AZ!_jU(CJto4Yzam`rn+s z*h8ulbmudcSAV|KC`c;JA1J)!!c|;FHCcnFHLrQUnDnmcg#26;UWU5j#FTOC4IB66 zb20kblb_rN@oEn)n!be&`ba(G6UNGJpQ^ mf6JTs4*bZ)Pw2G`W?9(+POSHSJKz|Ls)Pkca4Ocv?fW-pj{6G$ literal 0 HcmV?d00001 diff --git a/interface/resources/images/loadingBar_progress.png b/interface/resources/images/loadingBar_progress.png new file mode 100644 index 0000000000000000000000000000000000000000..2b1fb089d97f0acce5cfe62a07075328ee6316f2 GIT binary patch literal 20396 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 literal 0 HcmV?d00001 diff --git a/interface/resources/images/loadingBar_v1.png b/interface/resources/images/loadingBar_v1.png deleted file mode 100644 index b5c77054c4b2e185da0992a2237d2eb80d9033fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20323 zcmb@tWmH_zvM$;XA`n7wf&>UO?(VLQ26qV1xVuXT?lkW17Tn$4-6gm~<1{aOpR>>2 zKhC@3jyGQaShH%*s;^{Lt<^nN&yeqO5}%OpkN^O{Cy=D5A^?E!mjq~ic=z{uihq~) zcffTNQ+HIdF?Mv(w>JU^fo%+oh(T8RCPs=z`d~M^AtPP@;EkP`vbv+XtPHoIjTOEA zKV;}#t!)2+1>hBMwbeJYFmfa|FfuW-<|8?8=^!CC1M`unvB@&Z+KL#Nnn}9b8!5TV zDI2<57;=G01o(-0UAg}PSQ$C$6T4bjT03yN@{#-#m-{dMk7))H;(v-bTJVwlms9Gp z--$(R?2U-o=-KHEnOK>LIk@PV*crK)S!s!x8JRd37&#c2Sm+p;xEa~GS-6P*`$O{A znmyQz~pNj*3SAcH@7Qc2IV+HDXXSaG&n-B{NvoIHz2op0ClNcwbxEM1h3kMe?hX@C|un^O~bV1e* zj{4SyM*p&H_Sg1*>vH}db-6|Cjr1LD?3Hb7EdNyn-%V{CZ5&K(Y>7ogh$+>~tid)e z4pjfxq5le4)X3h<*$6CdZ(~LL&lGc;{V(SK??V4CeenO+)G_>Zis2u1@*j2cuc^Pq z_z&`by5KMKKYe6m{kM7S|8_}9^ymZtARP=66;gIxKJk(+%U^Pu*K85Fz0YmqH$XGan$Yi*qS887OUZLhEEB}c&c^P0%glH>Wd{pVNMo$j&L#v^vv z2e^N>`adrhraQ=q|7HP#|3L!)@c+r7{wL%6pUi*VhyFD~{4e64ori&+-4#i3?OauXCLC`|Yqs&424(NT4#b-AwN-2>vIG54`OQ z-y@f{-s8kl<$nlM3X^x#Z;TMB!TajiZ|Ir~o!3;p|DSbV397?|4u+$S!;O(1AOayP zTqme|Lw8}ea^Yx0&p}U~_}?~sB`6;#5;rqT^(JWGCcWtpzg=_&`@RPO(u(z*a(vp_ zts4dOl=6Jl{v+_tTww%EsDY0WLm}y^7X1Z=k>Muw8bptr%*x?WM~a$Mw_a1;P`{vrlwUTuJ3 z+*>|Z7w+2^?jZuqGhGitI$h(Lxava)}Mu-HH<&6Pe<5k^KcXsf972}HcT>cUUf$06UQn`)%I=2 zM0;uByfU9jn4c8G-kj3(A&&gwzJ3N%l(1SkU+S~3mvBM?xczfNt z-078|+Ff|ZJ10}0*`)Nq!Fi%wMxgn1yn4s7e4<4LTiE$i^}a0u0hIi5QAz#kylcs1 zdMXVZGkI@Q`d+?+0{TmR^=`NEC1&}Zd}99Dd)g6-8`uP(sdr)IOu`5I>zD}Q3G-tSUHnoYn$Td@k!eL*OAQYhN(<7`*X2EnLL!zFampog(bcPtxfd=y3BB%=3(~Y zw9JvU6`KvOr@M}ay@rkpA3bkq$IDUT`t!6}hxh%+!qKr7E7Bj6GbIb=EvX@3*vS(I zL;HOWEH~k$VfZzc?b4@!{Dp|OHG`)$qXW9{eKIovgJwRDmgDe0ob(BR=Po9WWhwL~ zA-t*@jis-FYvgt7Qf_W3Bax)HZR82gmTLoV%WkE=mdb(v^<62j28}G|DzT7J(sG#Z z6LCyWL6ehs6yF3!OvmwZu{hu2EA*~v84;kNuc8))IEUr$D?Jq zOVo65wd96Of};$4I4xr96${yrKxJms zn(ACeS=fZ>osEdGJmHh$$WD^Kv8Zdixbf!=mDohS1x0q!cGmeBi3oD2iG_Z(ue?_4 zV-yZB$N%`0YoPS}XYI9nSw|#8m7*J@`;;Hm^m28QHe^8*sp)!FXAu#x7Rqjl&e1%! zi2ljiEo!IeY)mGQ)rq(3elYCZ(#_-Na0;FM1DNe`)y+b%aftc^m zrFJ-jSz3~0-@P$=U0C&bJk!SXq_jt?5+h-g;IHxG8paqxqN9aQP<@xJ% zW&5pk2P`$H!~M^NJM`kwnCy*iVJ!9LB8lw{lIgpj%NU#R|9B{gd;`?3_#`|NVZ5I6 zxAON!30%e$zMjeZ+zt`JV$M6foti%m60oKzSg#U+bVg>FqGwg6fbjU+!7M~;=WGdy9u^wTW$Yl zjRP^W^n#ifXO7gNT|XoR{p#Q`7nvZ>D3YrFs?h4M82To;HpqpoMqL1q~$AKa;ke`r*F?ms;!cI zY)!|^8NYiPh-#$vz)_;T*h_Nu!9)*U9c%51viqbi{v<6>KP|dd<0tjAVIk+3_5I$r zC*3qRxBhN&?7Sf^)+ew*b#c)TPtMG~X+cd23VQ4+mFwRVxXiLABnJ=SN~qDJpIf9e zyVN){rc3HchmV%*6~+@W5N#_l-A~nEcWNEC46mm?R5l$?5j}b@>s!#L_z#1UzQ)Lu zpHl&*bFaqtE;6vF>et-S*QS(bpSVmfn4vr@H}hq6EXw1ycB zis040^vFXWA)x!GL#;C(l2bCZUQqj_*C$~iOBmSU*ICTB#Clu#EeOG>Zh7Gf2@ zU9-$TtrdwtGa6CYV3ZFxPBh2LvW{_CVUjCY9sEVJPL*%q%^nwLZ} zx~z9cjewQ`&VXv^YDLmGUA4|PEjeo40vLwBLj@LlVs816t={BNWwB$9Q<}n6G;(Xb zGP=k3d8vw9GQEQIhf>#L_ePV#eIf&P0qM=v_`juLO$HYk>TBrH=|YWYd-J(bTJG)dAV6cu`%W1+TOeFUp_aVYO~1hNQfBmL zujk~JAbBN}%!@LgA6j$VumyuKoxBRp4K8vyD zUFzB_nvORLQ`rf-Qi{B&6f4Wi5+?}+PbDHc(*sq|>nN1B8Sh*54ab&o$kz$FzKgJz z*!7j4Kq_$bBv5gD%Yuu>{K=m25sMvEYnBy>|Hw``!yWH(dMG#X1G*JrP@A$NCcdoa zSxUr~W<0QZU}~9FzSlhY8T(Oj`~rsas!C=~^0b1Bw}pBOu3BJ@uo@xu7ICK7O0grW zlY3HP;@E+KgSXY|X&Ez%G=s006ML)eR5?gOBkb=AJ)zWm8MkFC*H=i>Qi;Up#mGSu zGaP%6l2w0nkFY#*vJw@FN&^PT2OXld28k(As1PFDd_uRW&2X@&2gMb?;kr9E6;*qk zlgiM2JGNMfl(;>Jcci%v1U)hit>InT?=y*ca*b?YV|@z!_7R@jQhMxsSY?PsCD2>>!Y%c`nIM0T{UPjmWzxnb zTqn@`!ys8R$8OEd`*;5*6=yNR+u|Ygz4W1eGCwQ5@>T1e-=U#RKXny4rUNfcvM(+^ zijC2e$y5$T)-C(tID_&&k=N{otO;d!>((QWsSBJUE(dV=(5+?G>glJ4yPv4)*6!`-wj zG}@U+8^iW;h+skuC=(m(L6U@SL#Tz}*M1gazjSu>tACi1EJW95L5jPin5PAWe5%&% zS2ZK#0%0{Tr4;8iCuf2sGCmeTwQQ+^VrkorX_gwcyl++tbb`hYjB*k{7lud214I;9 z`T=?43&51y45}%fK}g-@^`6HtJKP0vD}yScO@{E|SFN;Q&17qHzX28Eer`zvj$y7a zQ)}{KJr*v~A9{XTsvQhVr{)d18@UWWQ6= zg;cUHr4KLUhLsFq2`l}%<8F`Iv2R}}$KOcY(%7y=Ahy~kI!#TekFCkg?HPhY>E)`= z;O52t6*Mlv#~ZcZ7z-!*hlyL5he*ou(5qGzzo<#H-vddL-+~=b ze;Q*(c*fq-SS)mugc|K0T`l8XCIib@a|~R{LnedbSST86eFN&@YltsY;FN;-Qo$-(->Gl^Vn=4C0iyP5i9_-5Q%hE>^iyn- zLX*8BHGT}b5Uq=baI8$!?okLKsbJfw+%=k3=n>>T44WBBFNI{u5BqeNCids&- zwiG|ytkLU@q*=IF7uQUfjyXLl&$K~4lFD$d(oD?z?yVW3wFhEr2MWWtRe$O-!QC|j z`EJt6eqk)opeDWuWh#eiq34u8wRd_|acq=e2~&mYDkBwXxe0s4C#a?QNX3zB5>hYU z(v_7^Trx}GF$2C$=Rlyt)slsrguU$b%b3`$PFt^~r+B}Sn!j5*`^?6G@FQ+GF9UuA z&d?DKCfjn@_0JkcyCUS)V(p`~gRirR`!SP_iX;B4A41}HU*(h|x-LdMkh`%xq@o_? zmSd!4*Mq6C)~D>Falp4R-#uD&?BA$s4#Mx%omoEiS9hP?&Xp~aN0tupX93q`H4?)Z zt$5nsUoR}nm3=_9v8S)qsU^_X7~ztCg)aAbkK?#q^_lR`Y=7i`fFl@>ts#0;VpzSu z_Su_p@p<5gtbW}&=A^!GCWEh7NaMW+@FK(enz{lA5Ph{+Jpma_FFOJc$vgNRx^f?H z!(Y}Nj}W=ueczu}TvtseavDGLJSll)S~Tp7tSd<~h+e`K!X+acK)NbZn`8_~`9$+g zA4-U|H!>Frh436rH$nnC93DDEH5v^$yof!Kz`gwbnnF45QAWZ^^}~6T@IBJe0UYX^ zIzk=(J@Yo*Y36Up5#+^P?nI>}m_v5sD1?Ri)_iN)_j;SClHL!ia!o5g&Yv=gkx`$S z+!HS3_|<#=G9*hUGs`{GoA#4t2X?9qri?U$7_H(l?!GL>#I_GJXF}Y)o>KJUbB9z2 zDabfgRpMhs+c;6g-)4qNN=o$8Q5V7v@Hccw>W=`gb6jL04q@grbJka1xM17c0;aLP-@Hs{A_^7kGjCq z@8oWiQ&3(F^Iae=iC7G{jfBo@js2A}z#x>^`9^c-0FepF{}9s`d=i&17r8PsO9%3C zb3Mt4@jYe@3w`N4)L?>%0W_2y)5OwG`7zV=6A4@8w1B`=WFh=N%vI0dnaxWxd&?I$ zGo@eJUkIB<0-RQ3%gi_}tccwK^Q1G!iWOzpn?p@t=O8!DVDkHi?Ag{Qe>i7U0t)3_g+*&h1IpESe@ocvM)yMFM`y-XafG!wrW zKds}bY`T2d8q<5i_kptfqh8x33(WxX^Q%wJV|^QK!vzlU*uE_J@Qn&s0C@sk`L?Ga zfv0XQqR0ChlYQX@`bU`jisJcQN$Hhqrc@i7m`bk4Gc80}-EeM#V9-2f1@VVipX;w= zSu#)271`%D8*PamGPOqAgiZ;e$;*v9CV3tS(h#QWtUL{!y`9tQA~7zbySHh@7BX)q zn|?F_>bPbrB1G7mZALk3O|y*pLRID{yA}agNKDpAy^$32n6bs@ zHLU<`wEiVzREFa9Lp+`_Y$_YFt&x&AQ_Yo|njFq($upKK@oW~Qm8uBoUe9#vyS%tr zBy6eJJtrGM83PPLG!4pJaCAaGnnc;?9CQB0)&^$1;oT_Lu+C4D0g;6Wt-6#8BZ|yX zSjx+-s>~0kOBrrqtN8|#b%v(KqCW9}lLrC=-?R_9u7fEgTDqRYUM{19n+c29cu&gp z>+#j)`+{P9A+nMDshzAwedrur0nEM}zOi|)!TjZGxo z;y`$F+B_>#Llha88vV)VJ*c}v<3t&f+$tOw$vwIqyuG@HQLj~##ahi)E|VS3h=ZsC z$l>c(V742&RZ&uS$v;BX4bO>>OES-7VR2J3j9b=M20~MbWA7dK>#0jK*o%!xWzt%! zGwJ%g#gEK!lGO>pq~YL!qT+yU;K$*L%}&@0{gj~*9Qup50aX`!s=G5ee28X3&EJ!NVJrXJXqQ76R zIeR9C#NEd6-nfk9Xi~2d{#i&+ebSfRvV@j>IIM`K9OLc z7U6+ZB;ZyB95rn;4_FV(@5@E&9rJbFY!0_dsInxxGwkOjje0Z4p}2uQn8~w}jjYrn z)>+PuIE3MuEzF06E4Z*GV;W3&NVnRd{*bU3wwp+aDefg{2hvGGfY6+Ed3}{=>YuSJ zqBbu)*s8k<#sYKmy~IoPh3GBE?4+B+U71k#K7PiRVAjIQ zL81s#Yz-k1V3j5j;abg1L?01fLy$+GOH;d~iZ4(dvd{Su)@1gAG?iB$`6$ggc(ILE z}{66-OTm?fPsSe@9*zj_@9gSH~12l z?-+21%Ie^f91C@ura3%Rj6PJfQhzt*aH+jZmFD<8>7Xzmq)AI(4=bo7Cu47jxQgpfH|R6d`o%()Zpt9+58L0Vq4}&2D%fc_QF`(Zb_iHWXND#bW*bFvWG^Qqb#Meu((04 zK%!<;XfO8}nWg*B5l$#j{)2=^I7UuhNJb@X$QntnwPv~I6vp0mjxAkRJctIZB``Q{`u<%OEM|IW}GTC7>NN(QwqMEFxMGOk1H2ocz$TuG<84_3uIeioMLrMGXigKzJ z^WdLs*CCbZ>6!3+gSmZ_^V!}OK)lv{nzMi7!Krfp;QTSe({^FE z9Kww8ra0Gxaz}ZVB0%xnDEnZ;d|6hxs`N!Bk}#W3Kyq(kq9AxEJdY#UG{j0Tgrjtv zS!>im)53r=T<``v)1lZQw+>tCmRBx3eir^qh`X}eA1z4ce9u|yZ=5u?>}*8DgfAQv zfxYRQSP~_9Gl_W(7NXYPt2CH9qccV`O9{OUSvvoaP6K_EL&&DjDq zI~xAN0$D$aK9c=KOH#zd?0JCt&{p5L+5A)R@mp4dK!Z<5qy6fxIc;%Z(URT)NFU^I zx+D!f_I;=YWm1_7BFTG$LJz!yI}*ES`44ds<|B`NN&HszbG}7sDasx<*;#SKYrf?X zyx`>U-1vMyW^I~2MzdeiNU;vuzcmPW`i7Y3NX@3&?iCt3?c9-KGWS*QXhINEwLUjb zwBl2SknvX^>D>|>@_Wm!j_a(qy{Rd(h#xwD;_4Ngo|4*reOJ1!6ij*iCY||;d<#cs z$^qyfUlvwAnu26-=)oTY`KYIyZ$GT2`q*Q-L2gZ*wl3^ZAa_4)lXKfgQ=VS|W9v9SEHrN~k9|#u>Yz{pJeS78 z{W+QccHRFAw?aMCdr&HNd44in>16l{W zw?=a!)iN1I3?WgQE6ic_wAFmt4V#IiL(caQ%=0Vy^DL`6L{JsG-R!Y~+a+3^haNaC z^9VCgX~Y7>ec19RBiez4QN(hFW>-9{EfAT}BC#CyJQ+ZF(}g{&a^VL^7ISed z{s`z|0g!Smxx7XL&34V-GjpOk4~7)z>Psf*j;VZ-G4)RJbYAlH6{C-N!w<@+!tQq4PFX#n~-hgb08VR@Y7A5v|gK~83h zwvtLZf7wQ#6l1XSSBPcI;7@i&o&-?gRHIT%)s1=+E-2M(KDqwN>%&}_zvu;2!aqct zfVG7Mr%%pqB;J@D`vI{89yRup*6+kx`Q$b;-@PZbUiQ;tYESP}{aM_*9R<61*~d&h z{rjziqK+-l>DyzlMJP0+aOX|eWm9L={Mcw6$MU6zr!ps%aBYBB_RKVUHS*{S^x5~QsnMtB6zVdL1y-avd zaQX?d=oYdpvsxQc)FV?)n~$k~?IzYoxPH}Z8ll{x(@}_XRB*}i$Xdc||Cy_Dqqa8F zY3A-ZlZ@*@LvbSVW8v`q5J;++d_-6in*}0RqkrF~vp6{7zVE$3>_FJ#$n$}RBu|%R?lb=_ zA>lELreRd3)^>&^JMjw8a`!+tV0-_MVo|s3`ym_jcS7)`G*0I5;(CVLHN^ zP6$}?dmrrgdZ6Mg-&bRiWHvoyngv)me=TQO3<*2z1_AqXQ=Y<)JZe3La2(PPRTC6% zsahB=syDRpo+_R^%n%02yad3N4JaSO8jmT9k(=teTz_VW?pQl?E^qrLQp!t;@oCm! zk+NxNsQ{)m>y)Qk_^Wd)DkDBVw-=_5o75yMSr6-f=L+~~(43?5*o!S?xg4Gv?4*qi zbr@R+=lg!MJD@vQ$BGGkiTBf1LmY*tAlCmK&O0C#f845g`Th)G zPd{+%mMhl>d32myYuK0PlW@?5eX+-%LL@9bPAd95td28{h{Xjd_T6;t7pm4l= zBQr8esg9ALDK;!rsKPzO8QeMzYhM$oMEFFFOlO7{lCXq+>@dt5d_HQD;?<`r@Rjda zp97_Z8ZmTbqy;StUvK$CX15d~TS?T|zIYg(XBc{Mn`R{`Xc^cWecDb(>d>+Og_J_c z(Ggjt^@G}z*N0*cx-N{lBTZglwS~m842(hVXf31KFuz zT;YZUn+>L|R~Ygw)Lk!ywtk=bjw0s+zJgz7Z&n@1`td@afr{F?+x{a!7m(9AAGXqn_>F9j%2;Uy%E>*$;P z^{TJV0@(@BqP64R$}@m|d}N2hxK#g6jo-*C$*H(36*U{g!;q}3B1Zb8Hdbt2n(&1-I$k(e#>yJzGo`Th`ao!Eb6yr zWR)~ENhQvk;fz>jx#Ztd`_$I0^3j%ow*aYGdazD>A9dVglDZDnhxl)W{?0wYiN+Bj z%3N*JycujF1xMbi2C3QuPAV$f#fX1@^BrTetnpw^>aMfw=#rAlYNRZzTsxxpXt<5sbaqH(Nm@jM~)D8DY++7cr?1T*66grTf zpvh8EDUzJ}Y)*DFWscG5V>j+T4smgfLBbhw`TP83j8@Yz2$V7>0~ynIhlkP>?VOgD zD0Lc)k;Hn`6Dbu!#Bvqh;2}6l;qlaGB=5Y$NCL-IBa)Q}XPK?#x3X_`M*-h4IJw)= z^Es{d^OOjyW>62&0)%UrCRcuWi@c|xXhp8_;3-+oD839Ppo)i{sZ%kQwryy!KdHyb z4E>Bf6atS)VQI|C#s%015!Nf!IV!Z+QKzTjK|Wvvfye^n&^_2;CJ^;_pyH%xH-w7l zYEl$E5k+pVZOAd}hsWlk>YdX%i`xbyoU-mz*K$yrNxVOT1C1qPE*#)kj?>GF%g>lJ zkNwIP^4bTpFMG_)EP2K0VA#>#a#?SarYL;P(dDP`DMA9o-Vo_ZL~h>~obleiXXtpi zb!j_);35eVQpuFyKf8G+Vq2*NS;ynJlEUv&^+k*2KN;IABsrg?39EY<6Nl7%nbqL3ZRd;G=01DU1O<=C z)D$v%6F#-8)~Pe^NP4hwmSHCs!w1ImdmYf-q}s0)0+Iq$en2y zOS2Kjp`v!uVgXfl`5CgEhn!oWWFxDV`Dx4W3I}C}c9Bg-{mnFa#8)c}u=XaE6%FnJ z*Kj)>obhRlnDA;-mcMa!%ty$qI>W}J3dCu#&GN?9A{$lGSk4YI$ns_?2h#9`vG^Lu z85G%*O_@yHbSVd(Hx&1|I6J1XMheJC*_^90rt?1(=<@8P#CWdBu#YcR>;ZqY|8Iu~a;mU@h{YW*-ET+)PY`k? zv$?Dzi7g>x;7@eqO+gy#8ra^&o%%SYqvU;T;;&G0Sb;tnaFodSiHp}C%r`Pri|T1% zBt&+Cf+>yUlEW{n+Do=Q&W4Jur}=<*?G*-*?W(a=BgQ5Mr-=q5)hD5!dQI)vrKFFJrgUdo;Xe4C6~`7v&Rvf%FGYs z^)ZV7m|U@5%1F*Bfeq-L&tdy|4D4oFln7vsa^ya119r}84+}0D+L_fzK{r;0@K}k66Pw4KC%sui|T#e|6QA;mQ z+`HXfhR3Zf%btrG?OKgb15KOXa=?02D%HB#wK8O8iu-XWQo1Lk=kfm5H!@~Tg$X2K zZZNNJ9X!1w#Gm9Ax^K%|l8c=|nP@Ag*Hzz-)Dk5Rm1fG>oBNWU>n3`Pl_TZ-Sc$%& z|Kcdc{?TxvY}5?xFO<;R>!vVUCMMT5o=ZJ-GvH}hA!Iz7&<6#Vb~|LP2Am#zo5ANP zOu)`9Fupo@en7hdhMdacG*rmQlPbwv@T6L-89UVh&30T!agz%&I`*Q6B_kzyLL7SM zNMoMWL3(Sb*$>*3=<#Xl_=2F;&yLccdGg~BE0R@E53o?Az9|>tnL^ZV7EIc%&wLYr zAsG3I%v;!Dhch@59qNU^P&kt!aV1H>6As zW3Ml^q?M~91*B%pmC{)(SfcigE2SHv1wD~Sa@PdQt546sOcC<0Fwa!oe!?^GVX@0__OX0bXK%fR70QA8sf>8s`D}-wHvWckfM{-l+QDhfqvr{bNuWLm-CJ6 zeW*)@!%CDV{$5jsP+_fn6wm==GIB82Y7paDe2p07NhHzPyGoDgY*;msBwJ&RUxi?K z;gq{bNZ? zwCPmA0?<8lC~XX0ep>T>O?*90Y=>4&yqN8XdaL>jzeNnA{v-b>S7u`jd=vY9$}w?G*3xSH5ML6s_?T$+PP>T-NyzEX2@FCjEezt1AeAz1 z+ilckj8AmeS7h*gKePx%wZEIK&4-A~bDH2wr(`ib{ABRnbLdAoHcxSOwQ}HTqtEi8 zSHqhQcTxRX+YSyWb4lsVXO5TUvRIol);@3$m-{wP7NxBtwW&xtBRa^GE?z6(5~vZd!gIc z=FvjRgyn#Yc&e!xeA1%$9i?d}wVVmF zD&87jjPdCUOT$OL^4OuDX!8M!^4P(6Y=0EC35k#~WJ}XhXc&%kIg?+h?C8kHs7830 zG%m&Qq{mIe;~D~`P;|U=S492op;DYQ$Xdkuu-1HGAa$1C2n+kDvY^2&3wt0& zcg^vEBH-gEgu}?e|=bo~OKnc{*?_9EXM1hMz%VHuA@; zASmpN__NHpjV#LP`eB$mI)97I-4Q38pg4n1U5v?meZ99yggHj3@S5fGvJ(mfe@n`` zF<+Nqw;ya=qJg*<7#FwcXh~B1K9V1*vr`bZ?If)S+W6&~iKJAO??_N%WwLW=84=rs zjWe4|mnNe+iDK0~zY(Z|D5eFBw)#pCwUs18DkZIVHb>CJn68%xrC{)d9S~k^rRv>x zCq_2alfa#=enzy#f)^ZljY96_30nAjSRz%v?ODL*=~AuZd6`GvXL|`&F(Jns_>r{J zBK||8Lgjs3^R;r1pk0k@^K~=7v$hW{E-98OmY#jvM97{IGew+;O$;Of;k^)#!H^`H zTX%3Ve>eSxO8i$k8TnsVz)7&wel_hO{6^cHK)_kkyjRr_CY@Z7s;Uu;NuStI)=jgC zQD0S(^5wE8PNuLho{}T~F*IXn&e?!_%{4zH2B+IKl`8~R?Mh~aP>Kkc|N(m0lxS+c|zfx0y*MO zzV3LT@w}ov|HzTqs6m#)oJCKqnrV|@@G!hiNUeP#a58?=k~Y>N0i?Sm!4$a++}Ot@ zr?tGL-X>X(^A9LpE??IJ;1+3vauB@M~_)uvYCoXDI3i$3aEq{Zp~oEI6|VIYD}=C6ypan<&55I!Qg+xbhujl?Aw6bX z>vx|&IG(&~6#bgbmkU(TQ^?|JIR7305_=W++gzfp!C~)7a49d|?o$wVXQooj2ZkdD zCfTzFdh2M8zS@*T%RRBoxUGsrLYlRRS95Z@JM$5!*q@HH(bw_vs!ga($K$f0-qVo{ zk;7?;i<^x4$zPzb#qd~10YqQVnd-o63_lMaQfMaGtn-QQ2~$ML3^k0z(H5pS=Fcmr{1ZHEc%h<4aVA}9no44lkQ-lKNq${6T;&)9O$mzL=<=MGO z+wYN+%c(1Ixr&SI`i%K>jOmYM)cR$_GueOI3u|6*o83%U${fCF0vippM4oB+L2UrQl1|sXD{U#UT)=Hlw&dDg?prI5rH`q-b z*0QLE2WLBVr$>!D83%VHLUeUN;d7ekeVSyrpxsgoIGfdOJ=t41iv>*X`M6MjwDV-y z;BRe*!YW_3ZTMfdh@N&`GM;EQ1RfO{pWq}d5IL2oeOX!r>t@Pm_S&PU= zn7@wS*ErbkC*^W2gsVYSHr#x&OL_e_h5h_Zo~*HlCB7S-tB|vIx|ddu4>$J-NutkW zC=~DZdhx9AUInL$Orjdg1pOlOm+T1_L&Gi$=_)fg{@NK~sXTOdquxe6af;u}Zo9Mf zJsGjL$%gjmgET3F9?jUZ@^z(5u?(M9cr39;J-s@7F?T4T!2s@|8GbR2AemXSzr2AU zCFq>$Gj8haLwGL9ep+51F6THnuV6#{3dx0fXtx-hta8o2%P8z@bNW{#Brp6)7;0KH z_;I+ug48Cluf(Z4!Z#al*Tv96 zx`ZtypH$;F5K+i5KW@2PXjZW10V~F=FIhy9Txf~$iG%8A%h^V7#qu}yL?|9Ah&-f` z3rbGtV6SAIyXP8HAR%n3Tu$1A8ZXlR_W&JDdq+Du&BC{P$F_r!3>_;uOZTJG6M`B3 zElTGMD%m{_c01{cYeVrI+7PS7eEc1f*k}*YxqK2O=88y40w3aD42g#^^Ml@V8 zm};r81}kYq!_0eAX*+-w5vh8LTlA1Sec;Rbd*8yDuiY9H&c2v50eDnmRQdki?bjF) ze&E9L&zBYx3{vUClOKGqV?~9IGFwt!qIotR`xD#Uk7+uf4~CIC4yP+@ck^_BM0WVr zOe#PIz%{qMiK2af!DkPr{TZj@&St#{+RYM}&0nha;D=De_p9!0*2tfU8_#oB;w!%f z_W1Af$l*?78D0~O9FpCPlbRJtWQwC9dB)tn&V}MKkl+A{)MH+G+@baqwGxRk5O_<* z-bTs}h8o8yJ{xyHTRQg5bBA%5f~B97q9JY)Q!v}$w}GFL23eHO*3;q%?SURyGyyPg zyP`quON}9i29@#&jDv-@x~o28KngPe9YWrFeB`EjniAN(XBm9EsgI6l1aYT{F{`AlaEr?Nu6bMSH=ed2*e)4Q4O@j!tuT@>cyLnl#w^> z^VwTpaS+m%LTA>htC~57wqVnj+g$1&W7BalQDeBZjk40`^e+|Um8q)yP7drm!kt@3 z_EMA4R2NN-uO?jz-k(Th0#5gihcXYA$kPsen#iGHn@))3iZA82i8YltmQkH((pic# z72Z{ZacvjFZK`mOw(X~I1D ztWj7?JgDYcj(kEqO{vyiAO?gcI{jkP%N%-PA8`($f|rrj&flHQBQ@kUcmG*8(*bmk zdLeP638FVYYXV+fq$$_uzM&XX-TreqX4gT8g|n?`Cv0)P z8^b4hiuZZG)_d6UxozTUXg|lD8WOXRYv=K1D2rHY1BIPG*@b>lImBWqRD1jN@taGy zv3Sbo_a1+`2}sMo1gDzhtN5`b(q-N>d~&w=IFR;hRK#W}?oj;~dPm*R&4I~`{}d)p zU;fbX1j*2>cH5t!s}EIQ{l!6OU}cRyQFIZ0q}&(9*hXicr-J{dlk<#fLdoKIsDgqJ zKoKd5KtL1Z2`uF)iBgnEvmg*6RX{odLg>W;gkGh0L}@{a)EJZ=q$`AGf>i0fB#>l% z@9b0egZp96nKSL)Gxt9;-{#Eyoz48$wp#KC3lUrTguMRw%XBH@HTR<0T%X9q#P|VQ z&-iE7(5m97Qy>J7g=xS#e*(l-th9gaB?ob=#RZxcvZStDiDgGiR&6$#FmnYukf|Vq+Njz^(*c0_Bl|p{1PTYr-plt5Q z$?j|%F62(y^;CB-1bec;N2wR#Q)yvU5;hM%9wouupi|g`HUl z(_r61Yp~)vIDXjs*-2hykm*UA&-QNdR-SgIm6yvc*OFwHFSpH!Db3C0yn6M*qYjA(ut}5Z5mO#?i0s6xDgxj-{qF zY^Woqd9Bey5Ba$%z+TYBJI_kPvTqeq=G11h6$zp;Y|01Fx$6T}3*7GIS%-bH=w_RN z34{2{U=&-=__?PwYH@L;kz&e-V*{QS#AKD*qiCb+#7ppq{==@m45M8Tk(>#^pXdgK zBpKa_S9(!|r;aNF%j}0NdB1ug>?aVhrwXwNju3n`V4yxW3=4PL`7!cGK*Ea zoiV7RME29xaY~EY`(4Chd>rXOnVU09V)CpW2lWw6KOOoBYf@}ha0g-iXF?fE{w05- z$~1&b!;}!$Raih=Cgir@;H8HIn_YyG@K!BR-q5kwQOIq;(s2&Hrl!r#@KD5uTO+^q zqw&nL&Hbo}@ROn41=*m4`CW-#J9@Sqq38iW^|gWN@GCh94L8Xzk>4Nfbu+$lrsCQX zpJ^eiVb#_rd%21o#!gos%E_$y&$Toj%~2XPwh5+nr$qL&TJDCH&Rksd#5@MQ(;u={ znI1W^UhooKwFCaZEOktW+8u*E+1FU|s49BccH5@qJY1fy${x$CteD=d8F>tr+Nw8U z#jN}Dz?3Nx?z8qM~D$y04 z1LgAJ)UfWb<2cu*Y(6e+sy)?=q_(6c`pc0=c=J2A?Qdxsx*xUIb#>lNSyoc>p5DUC zf8chLW>^LZgx#BDN4AAY;)Weul4Ru&v$>`>yv-ptGdeaoY2@i=44VtQ{Jlb=NW(d$ z^G{zC1O;BN>Aj+3WbN8gDy$BIFBk}#$<`!o&RwJ5tG|&@CoaFWd`@fXPX#=GJ>vp~ z+VPnJyvLR_OVRQ%^fH+2Wb@{4LJz%-wsU$RKPZi@@vqf|;={@xIp0(YG_Xi^lEka( zt6=>31`2`M{gnfiO-<7yO@)iM_YU?~vb|>MemhL`B^b4N^h?AHeNTv?zYy?^9gu7Q zc+-gs+?tAzS%lRv1a!30D_WK|T9)uFOA9SasVz%Pp@bt`<@4VQj(TWzuni_1)WWuU z%VJd08_RB*J1Mw-)!voUau#PLH_8k}PKSLwdKiI4g zVe!sm{Az4m;ql#!#xb+-0wW+n@PurKNgI>3f_{74*^C|4{n|;PMcI?yRj093?1sF4 z%1e?gr{(Y_e6-#MZ7@9y3K3YqeY6ygUa|c=f$EA5sG2b!bH~+zu4Fbs1jju#uPpoM z67m-}mr}B@o)JS2#={a_;~d<93ewJE`|hneQ?Dv@BD-kwmk+B-C8BD2Ost!~xnM@W zX^TAVx8>?6k~_9C(j?=n+dr3PF&Mm39W>j%%EMBxa69qO%a-ykf_ZJY6ba1(K3?rq z`|ed&xjuGNRzo+kzjTCaJY%O1IL1}yRJB!OQpf^9T)%U<_l4_xmdB}wQ)4#^%X1!* z9xSHkb7-aBIdcNe=a`VWakP(M@YO+Yxe++Ox@m57!x<|8_L7~fR8GI|)<zT}Q?7!`$XKxL^p(mj+S(de79 ze6;__FgiK1$8MzUyfIZafzWP+D8vi0xL53BH^fuoH4EYK86mb(6$W$NGN6)GSEF%v zG2F*h6j;?hGzNEuAB-`H2n~$h%YUOw80|dgMlE-O7!$j!d&)-Q%8=)K4^Ow=%k0yL zwS&9?*=p3pH{EsgpWzC=!yUSnfrCq1L`r;b<8$mk*Ahq>a)CM&j@C@N1mAEJ8X zxp7n$o5gjl;(>YNjb#pSdIu1Eov3?t8X}Ne|4L_DT-5Y#QRPA`)KOvE=LBLPI*;!$ z`cRuU<$K1X=`Aa~yDpoD%kebe5y6DQi$r(DBT~(Ks}t`wb{%S6aI_tlD;vIMhnqcw zz&(_G9=(j>Q-+I1H(_W?z@TIdjU>bHo}E1hA3Q9wZs0$y2is_$H&eml9_>?k<*zVB zujRfy{>O?-!%sX)5G;IswQ$~Q{2LPE*9aoJ)DhBmJhW*fS3K+;ff+D##;-8mKl3n| z!n*dE%foNFV}PYYAsG4CUcjpqhbft%bD&{AfGz86K!)d~F+}0hAru!wp7%{e5Ry#j zrOy_-%u$#qf9R89WA6bb%3LVYN&BQyG`%Nn-+?jc+K&YQfEaFu$*65)qu*Ud>@*Jz zwOEeoG@zA3sDZJ}fFi|sAYg@Q0)wV=o=*Cnc%}W0?HAn5_waxMp8XeK(p`31=8T74 z91E00QxgMdzC+8Mo@Pr^yn1kp;4l@aBS9-$Yr@k+=|5RKA{sn7b@#~=66Eik9FL*w zZ%`{bR~q2#PKXlOe9zu3P=`7eK#C8Lp&kML)O+Zvceu0m))FU<#wAl=vBmV3bAato zOAXd<%ybA@1vo+fLBp|TD2}sT zCm6X-5J)Xt7wSkq!>xxtmAn57pvi0d!#Sz$pV#?5|G3mo@Bgsk{+T%YPv`IUhpfLw z|HWv!`nUa`>X&E$!0&NTzqkB(#?Vx0D<7}q6dpSOMmSSuA!@r IQ8j<^ZyDs5-T(jq diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cac201f34b..12a3ebf887 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2973,6 +2973,9 @@ void Application::initializeUi() { if (_window && _window->isFullScreen()) { setFullscreen(nullptr, true); } + + + setIsInterstitialMode(true); } @@ -3451,13 +3454,15 @@ bool Application::isServerlessMode() const { } bool Application::isInterstitialMode() const { - return _interstitialMode; + bool interstitialModeEnabled = Menu::getInstance()->isOptionChecked("Enable Interstitial"); + return interstitialModeEnabled ? _interstitialMode : false; } void Application::setIsInterstitialMode(bool interstitialMode) { - if (_interstitialMode != interstitialMode) { - qDebug() << "-------> interstitial mode changed: " << _interstitialMode << " ------------> "; + bool interstitialModeEnabled = Menu::getInstance()->isOptionChecked("Enable Interstitial"); + if (_interstitialMode != interstitialMode && interstitialModeEnabled) { _interstitialMode = interstitialMode; + qDebug() << "-------> interstitial mode changed: " << _interstitialMode << " ------------> "; emit interstitialModeChanged(_interstitialMode); } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 88aea0379f..856e2d199e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -631,7 +631,7 @@ private: QSet _keysPressed; bool _enableProcessOctreeThread; - bool _interstitialMode { true }; + bool _interstitialMode { false }; OctreePacketProcessor _octreeProcessor; EntityEditPacketSender _entityEditSender; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 130c2c0b89..9b0e8d83c1 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -781,6 +781,7 @@ Menu::Menu() { // Developer > Show Overlays addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Overlays, 0, true); + addCheckableActionToQMenuAndActionHash(developerMenu, "Enable Interstitial", 0, false); #if 0 /// -------------- REMOVED FOR NOW -------------- addDisabledActionAndSeparator(navigateMenu, "History"); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 31510831c8..c333a0c8c2 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -34,8 +34,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/emote.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ - "system/controllers/controllerScripts.js", - "system/interstitialPage.js" + "system/controllers/controllerScripts.js" //"system/chat.js" ]; diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 149e1e141b..87f415130f 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -15,8 +15,8 @@ (function() { Script.include("/~/system/libraries/Xform.js"); var DEBUG = true; - var MAX_X_SIZE = 4; - var EPSILON = 0.25; + var MAX_X_SIZE = 3.8; + var EPSILON = 0.01; var isVisible = false; var STABILITY = 3.0; var VOLUME = 0.4; @@ -149,8 +149,8 @@ var loadingBarPlacard = Overlays.addOverlay("image3d", { name: "Loading-Bar-Placard", - localPosition: { x: 0.0, y: -0.99, z: 0.0 }, - url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/loadingBar_placard.png", + localPosition: { x: 0.0, y: -0.99, z: 0.3 }, + url: Script.resourcesPath() + "images/loadingBar_placard.png", alpha: 1, dimensions: { x: 4, y: 2.8}, visible: isVisible, @@ -164,10 +164,10 @@ var loadingBarProgress = Overlays.addOverlay("image3d", { name: "Loading-Bar-Progress", - localPosition: { x: 0.0, y: -0.99, z: 0.0 }, - url: Script.resourcesPath() + "images/loadingBar_v1.png", + localPosition: { x: 0.0, y: -0.90, z: 0.0 }, + url: Script.resourcesPath() + "images/loadingBar_progress.png", alpha: 1, - dimensions: {x: 4, y: 2.8}, + dimensions: {x: 3.8, y: 2.8}, visible: isVisible, emissive: true, ignoreRayIntersection: false, @@ -285,7 +285,7 @@ } } - var THE_PLACE = "hifi://TheSpot"; + var THE_PLACE = "hifi://TheSpot-dev"; function clickedOnOverlay(overlayID, event) { print(overlayID + " other: " + loadingToTheSpotID); if (loadingToTheSpotID === overlayID) { @@ -309,6 +309,11 @@ visible: !physicsEnabled }; + var loadingBarProperties = { + dimensions: { x: 0.0, y: 2.8 }, + visible: !physicsEnabled + }; + // Menu.setIsOptionChecked("Show Overlays", physicsEnabled); if (!HMD.active) { @@ -324,7 +329,7 @@ Overlays.editOverlay(domainDescription, domainTextProperties); Overlays.editOverlay(domainToolTip, properties); Overlays.editOverlay(loadingBarPlacard, properties); - Overlays.editOverlay(loadingBarProgress, properties); + Overlays.editOverlay(loadingBarProgress, loadingBarProperties); Camera.mode = "first person"; } @@ -349,13 +354,13 @@ lastInterval = thisInterval; timeElapsed += deltaTime; - progress += MAX_X_SIZE * (deltaTime / 1000); - print(progress); + progress += (deltaTime / 1000); if (progress > MAX_X_SIZE) { - progress = 4; + progress = MAX_X_SIZE; } + var properties = { - localPosition: { x: 2.0 - (progress / 2), y: -0.99, z: -0.3 }, + localPosition: { x: (1.85 - (progress / 2) - (-0.029 * (progress / MAX_X_SIZE))), y: -0.935, z: 0.0 }, dimensions: { x: progress, y: 2.8 @@ -402,7 +407,7 @@ } currentProgress = lerp(currentProgress, target, 0.2); var properties = { - localPosition: { x: 2 - (currentProgress / 2), y: -0.99, z: -0.3 }, + localPosition: { x: (1.85 - (progress / 2) - (-0.029 * (progress / MAX_X_SIZE))), y: -0.935, z: 0.0 }, dimensions: { x: currentProgress, y: 2.8 @@ -410,7 +415,7 @@ }; print("progress: " + currentProgress); Overlays.editOverlay(loadingBarProgress, properties); - if (((physicsEnabled && (currentProgress >= (MAX_X_SIZE - EPSILON))) || connectionToDomainFailed)) { + if ((physicsEnabled && (currentProgress >= (MAX_X_SIZE - EPSILON)))) { print("----------> ending <--------"); updateOverlays((physicsEnabled || connectionToDomainFailed)); endAudio(); @@ -441,7 +446,7 @@ updateOverlays(toggle); if (!toggle) { - //Script.setTimeout(updateProgress, BASIC_TIMER_INTERVAL_MS); + // Script.setTimeout(updateProgress, BASIC_TIMER_INTERVAL_MS); } }); } From c2612af7a8550e301a4861302cb6a9ae0cd9fc46 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 10 Aug 2018 17:10:33 -0700 Subject: [PATCH 041/744] changed the spine stretch limit, will check the scale implications next --- interface/src/avatar/MyAvatar.cpp | 1 + libraries/shared/src/AvatarConstants.h | 2 +- scripts/developer/objectOrientedStep.js | 683 ++++++++++++++++++++++++ 3 files changed, 685 insertions(+), 1 deletion(-) create mode 100644 scripts/developer/objectOrientedStep.js diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0d7b00da78..db0c683632 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3692,6 +3692,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat setForceActivateRotation(false); } if (!isActive(Horizontal) && getForceActivateHorizontal()) { + qCDebug(interfaceapp) << "called the recentering from script"; activate(Horizontal); setForceActivateHorizontal(false); } diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index d9b26927e2..6b35a45f4c 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -34,7 +34,7 @@ const float DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 3.3f; const float DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD = 0.18f; const float DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE = 7.0f; const float DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE = 7.0f; -const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.07f; +const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.03f; const float DEFAULT_AVATAR_FORWARD_DAMPENING_FACTOR = 0.5f; const float DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR = 2.0f; const float DEFAULT_AVATAR_HIPS_MASS = 40.0f; diff --git a/scripts/developer/objectOrientedStep.js b/scripts/developer/objectOrientedStep.js new file mode 100644 index 0000000000..4eca1fb115 --- /dev/null +++ b/scripts/developer/objectOrientedStep.js @@ -0,0 +1,683 @@ +/* jslint bitwise: true */ + +/* global Script, Vec3, MyAvatar, Tablet, Messages, Quat, +DebugDraw, Mat4, Entities, Xform, Controller, Camera, console, document*/ + +Script.registerValue("STEPAPP", true); +var CENTIMETERSPERMETER = 100.0; +var LEFT = 0; +var RIGHT = 1; +var INCREASING = 1.0; +var DECREASING = -1.0; +var DEFAULT_AVATAR_HEIGHT = 1.64; +var TABLET_BUTTON_NAME = "STEP"; +var CHANGE_OF_BASIS_ROTATION = { x: 0, y: 1, z: 0, w: 0 }; +// in meters (mostly) +var DEFAULT_ANTERIOR = 0.04; +var DEFAULT_POSTERIOR = 0.06; +var DEFAULT_LATERAL = 0.10; +var DEFAULT_HEIGHT_DIFFERENCE = 0.02; +var DEFAULT_ANGULAR_VELOCITY = 0.3; +var DEFAULT_HAND_VELOCITY = 0.4; +var DEFAULT_ANGULAR_HAND_VELOCITY = 3.3; +var DEFAULT_HEAD_VELOCITY = 0.14; +var DEFAULT_LEVEL_PITCH = 7; +var DEFAULT_LEVEL_ROLL = 7; +var DEFAULT_DIFF = 0.0; +var DEFAULT_DIFF_EULERS = { x: 0.0, y: 0.0, z: 0.0 }; +var DEFAULT_HIPS_POSITION; +var DEFAULT_HEAD_POSITION; +var DEFAULT_TORSO_LENGTH; +var SPINE_STRETCH_LIMIT = 0.02; + +var VELOCITY_EPSILON = 0.02; +var AVERAGING_RATE = 0.03; +var HEIGHT_AVERAGING_RATE = 0.01; +var STEP_TIME_SECS = 0.2; +var MODE_SAMPLE_LENGTH = 100; +var RESET_MODE = false; +var HEAD_TURN_THRESHOLD = 25.0; +var NO_SHARED_DIRECTION = -0.98; +var LOADING_DELAY = 500; +var FAILSAFE_TIMEOUT = 2.5; + +var debugDrawBase = true; +var activated = false; +var documentLoaded = false; +var failsafeFlag = false; +var failsafeSignalTimer = -1.0; +var stepTimer = -1.0; + + +var modeArray = new Array(MODE_SAMPLE_LENGTH); +var modeHeight = -10.0; + +var handPosition; +var handOrientation; +var hands = []; +var hipToHandAverage = []; +var handDotHead = []; +var headAverageOrientation = MyAvatar.orientation; +var headPoseAverageOrientation = { x: 0, y: 0, z: 0, w: 1 }; +var averageHeight = 1.0; +var headEulers = { x: 0.0, y: 0.0, z: 0.0 }; +var headAverageEulers = { x: 0.0, y: 0.0, z: 0.0 }; +var headAveragePosition = { x: 0, y: 0.4, z: 0 }; +var frontLeft = { x: -DEFAULT_LATERAL, y: 0, z: -DEFAULT_ANTERIOR }; +var frontRight = { x: DEFAULT_LATERAL, y: 0, z: -DEFAULT_ANTERIOR }; +var backLeft = { x: -DEFAULT_LATERAL, y: 0, z: DEFAULT_POSTERIOR }; +var backRight = { x: DEFAULT_LATERAL, y: 0, z: DEFAULT_POSTERIOR }; + + +// define state readings constructor +function StateReading(headPose, rhandPose, lhandPose, backLength, diffFromMode, diffFromAverageHeight, diffFromAveragePosition, + diffFromAverageEulers) { + this.headPose = headPose; + this.rhandPose = rhandPose; + this.lhandPose = lhandPose; + this.backLength = backLength; + this.diffFromMode = diffFromMode; + this.diffFromAverageHeight = diffFromAverageHeight; + this.diffFromAveragePosition = diffFromAveragePosition; + this.diffFromAverageEulers = diffFromAverageEulers; +} + +// define current state readings object for holding tracker readings and current differences from averages +var currentStateReadings = new StateReading(Controller.getPoseValue(Controller.Standard.Head), + Controller.getPoseValue(Controller.Standard.RightHand), Controller.getPoseValue(Controller.Standard.LeftHand), + DEFAULT_TORSO_LENGTH, DEFAULT_DIFF, DEFAULT_DIFF, DEFAULT_DIFF, DEFAULT_DIFF_EULERS); + +// declare the checkbox constructor +function AppCheckbox(type,id,eventType,isChecked) { + this.type = type; + this.id = id; + this.eventType = eventType; + this.data = {value: isChecked}; +} + +// define the checkboxes in the html file +var usingAverageHeight = new AppCheckbox("checkboxtick", "runningAverageHeightCheck", "onRunningAverageHeightCheckBox", + false); +var usingModeHeight = new AppCheckbox("checkboxtick","modeCheck","onModeCheckBox",true); +var usingBaseOfSupport = new AppCheckbox("checkboxtick","baseOfSupportCheck","onBaseOfSupportCheckBox",true); +var usingAverageHeadPosition = new AppCheckbox("checkboxtick", "headAveragePositionCheck", "onHeadAveragePositionCheckBox", + false); + +var checkBoxArray = new Array(usingAverageHeight,usingModeHeight,usingBaseOfSupport,usingAverageHeadPosition); + +// declare the html slider constructor +function AppProperty(name, type, eventType, signalType, setFunction, initValue, convertToThreshold, convertToSlider, signalOn) { + this.name = name; + this.type = type; + this.eventType = eventType; + this.signalType = signalType; + this.setValue = setFunction; + this.value = initValue; + this.get = function () { + return this.value; + }; + this.convertToThreshold = convertToThreshold; + this.convertToSlider = convertToSlider; + this.signalOn = signalOn; +} + +// define the sliders +var frontBaseProperty = new AppProperty("#anteriorBase-slider", "slider", "onAnteriorBaseSlider", "frontSignal", + setAnteriorDistance, DEFAULT_ANTERIOR, function (num) { + return convertToMeters(num); + }, function (num) { + return convertToCentimeters(num); + },true); +var backBaseProperty = new AppProperty("#posteriorBase-slider", "slider", "onPosteriorBaseSlider", "backSignal", + setPosteriorDistance, DEFAULT_POSTERIOR, function (num) { + return convertToMeters(num); + }, function (num) { + return convertToCentimeters(num); + }, true); +var lateralBaseProperty = new AppProperty("#lateralBase-slider", "slider", "onLateralBaseSlider", "lateralSignal", + setLateralDistance, DEFAULT_LATERAL, function (num) { + return convertToMeters(num); + }, function (num) { + return convertToCentimeters(num); + }, true); +var headAngularVelocityProperty = new AppProperty("#angularVelocityHead-slider", "slider", "onAngularVelocitySlider", + "angularHeadSignal", setAngularThreshold, DEFAULT_ANGULAR_VELOCITY, function (num) { + var base = 4; + var shift = 2; + return convertExponential(base, num, DECREASING, shift); + }, function (num) { + var base = 4; + var shift = 2; + return convertLog(base, num, DECREASING, shift); + }, true); +var heightDifferenceProperty = new AppProperty("#heightDifference-slider", "slider", "onHeightDifferenceSlider", "heightSignal", + setHeightThreshold, DEFAULT_HEIGHT_DIFFERENCE, function (num) { + return convertToMeters(-num); + }, function (num) { + return convertToCentimeters(-num); + }, true); +var handsVelocityProperty = new AppProperty("#handsVelocity-slider", "slider", "onHandsVelocitySlider", "handVelocitySignal", + setHandVelocityThreshold, DEFAULT_HAND_VELOCITY, function (num) { + return num; + }, function (num) { + return num; + }, true); +var handsAngularVelocityProperty = new AppProperty("#handsAngularVelocity-slider", "slider", "onHandsAngularVelocitySlider", + "handAngularSignal", setHandAngularVelocityThreshold, DEFAULT_ANGULAR_HAND_VELOCITY, function (num) { + var base = 7; + var shift = 2; + return convertExponential(base, num, DECREASING, shift); + }, function (num) { + var base = 7; + var shift = 2; + return convertLog(base, num, DECREASING, shift); + }, true); +var headVelocityProperty = new AppProperty("#headVelocity-slider", "slider", "onHeadVelocitySlider", "headVelocitySignal", + setHeadVelocityThreshold, DEFAULT_HEAD_VELOCITY, function (num) { + var base = 2; + var shift = 0; + return convertExponential(base, num, INCREASING, shift); + }, function (num) { + var base = 2; + var shift = 0; + return convertLog(base, num, INCREASING, shift); + }, true); +var headPitchProperty = new AppProperty("#headPitch-slider", "slider", "onHeadPitchSlider", "headPitchSignal", + setHeadPitchThreshold, DEFAULT_LEVEL_PITCH, function (num) { + var base = 2.5; + var shift = 5; + return convertExponential(base, num, DECREASING, shift); + }, function (num) { + var base = 2.5; + var shift = 5; + return convertLog(base, num, DECREASING, shift); + }, true); +var headRollProperty = new AppProperty("#headRoll-slider", "slider", "onHeadRollSlider", "headRollSignal", setHeadRollThreshold, + DEFAULT_LEVEL_ROLL, function (num) { + var base = 2.5; + var shift = 5; + return convertExponential(base, num, DECREASING, shift); + }, function (num) { + var base = 2.5; + var shift = 5; + return convertLog(base, num, DECREASING, shift); + }, true); + +var propArray = new Array(frontBaseProperty, backBaseProperty, lateralBaseProperty, headAngularVelocityProperty, + heightDifferenceProperty, handsVelocityProperty, handsAngularVelocityProperty, headVelocityProperty, headPitchProperty, + headRollProperty); + +// var HTML_URL = Script.resolvePath("http://hifi-content.s3.amazonaws.com/angus/stepApp/stepApp.html"); +var HTML_URL = Script.resolvePath("http://hifi-content.s3.amazonaws.com/angus/stepApp/stepAppExtra.html"); +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + +function manageClick() { + if (activated) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(HTML_URL); + } +} + +var tabletButton = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: Script.resolvePath("http://hifi-content.s3.amazonaws.com/angus/stepApp/foot.svg"), + activeIcon: Script.resolvePath("http://hifi-content.s3.amazonaws.com/angus/stepApp/foot.svg") +}); + +function drawBase() { + // transform corners into world space, for rendering. + var worldPointLf = Vec3.sum(MyAvatar.position,Vec3.multiplyQbyV(MyAvatar.orientation, frontLeft)); + var worldPointRf = Vec3.sum(MyAvatar.position,Vec3.multiplyQbyV(MyAvatar.orientation, frontRight)); + var worldPointLb = Vec3.sum(MyAvatar.position,Vec3.multiplyQbyV(MyAvatar.orientation, backLeft)); + var worldPointRb = Vec3.sum(MyAvatar.position,Vec3.multiplyQbyV(MyAvatar.orientation, backRight)); + + var GREEN = { r: 0, g: 1, b: 0, a: 1 }; + // draw border + DebugDraw.drawRay(worldPointLf, worldPointRf, GREEN); + DebugDraw.drawRay(worldPointRf, worldPointRb, GREEN); + DebugDraw.drawRay(worldPointRb, worldPointLb, GREEN); + DebugDraw.drawRay(worldPointLb, worldPointLf, GREEN); +} + +function onKeyPress(event) { + if (event.text === "'") { + // when the sensors are reset, then reset the mode. + RESET_MODE = false; + } +} + +function onWebEventReceived(msg) { + var message = JSON.parse(msg); + print(" we have a message from html dialog " + message.type); + propArray.forEach(function (prop) { + if (prop.eventType === message.type) { + prop.setValue(prop.convertToThreshold(message.data.value)); + print("message from " + prop.name); + // break; + } + }); + checkBoxArray.forEach(function(cbox) { + if (cbox.eventType === message.type) { + cbox.data.value = message.data.value; + // break; + } + }); + if (message.type === "onCreateStepApp") { + print("document loaded"); + documentLoaded = true; + Script.setTimeout(initAppForm, LOADING_DELAY); + } +} + +function initAppForm() { + print("step app is loaded: " + documentLoaded); + if (documentLoaded === true) { + propArray.forEach(function (prop) { + tablet.emitScriptEvent(JSON.stringify({ + "type": "trigger", + "id": prop.signalType, + "data": { "value": "green" } + })); + tablet.emitScriptEvent(JSON.stringify({ + "type": "slider", + "id": prop.name, + "data": { "value": prop.convertToSlider(prop.value) } + })); + }); + checkBoxArray.forEach(function (cbox) { + tablet.emitScriptEvent(JSON.stringify({ + "type": "checkboxtick", + "id": cbox.id, + "data": { "value": cbox.data.value } + })); + }); + } +} + +function updateSignalColors() { + + // force the updates by running the threshold comparisons + withinBaseOfSupport(currentStateReadings.headPose.translation); + withinThresholdOfStandingHeightMode(currentStateReadings.diffFromMode); + headAngularVelocityBelowThreshold(currentStateReadings.headPose.angularVelocity); + handDirectionMatchesHeadDirection(currentStateReadings.lhandPose, currentStateReadings.rhandPose); + handAngularVelocityBelowThreshold(currentStateReadings.lhandPose, currentStateReadings.rhandPose); + headVelocityGreaterThanThreshold(Vec3.length(currentStateReadings.headPose.velocity)); + headMovedAwayFromAveragePosition(currentStateReadings.diffFromAveragePosition); + headLowerThanHeightAverage(currentStateReadings.diffFromAverageHeight); + isHeadLevel(currentStateReadings.diffFromAverageEulers); + + propArray.forEach(function (prop) { + if (prop.signalOn) { + tablet.emitScriptEvent(JSON.stringify({ "type": "trigger", "id": prop.signalType, "data": { "value": "green" } })); + } else { + tablet.emitScriptEvent(JSON.stringify({ "type": "trigger", "id": prop.signalType, "data": { "value": "red" } })); + } + }); +} + +function onScreenChanged(type, url) { + print("Screen changed"); + if (type === "Web" && url === HTML_URL) { + if (!activated) { + // hook up to event bridge + tablet.webEventReceived.connect(onWebEventReceived); + print("after connect web event"); + MyAvatar.hmdLeanRecenterEnabled = false; + + } + activated = true; + } else { + if (activated) { + // disconnect from event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + documentLoaded = false; + } + activated = false; + } +} + +function getLog(x, y) { + return Math.log(y) / Math.log(x); +} + +function noConversion(num) { + return num; +} + +function convertLog(base, num, direction, shift) { + return direction * getLog(base, (num + 1.0)) + shift; +} + +function convertExponential(base, num, direction, shift) { + return Math.pow(base, (direction*num + shift)) - 1.0; +} + +function convertToCentimeters(num) { + return num * CENTIMETERSPERMETER; +} + +function convertToMeters(num) { + print("convert to meters " + num); + return num / CENTIMETERSPERMETER; +} + +function isInsideLine(a, b, c) { + return (((b.x - a.x)*(c.z - a.z) - (b.z - a.z)*(c.x - a.x)) > 0); +} + +function setAngularThreshold(num) { + headAngularVelocityProperty.value = num; + print("angular threshold " + headAngularVelocityProperty.get()); +} + +function setHeadRollThreshold(num) { + headRollProperty.value = num; + print("head roll threshold " + headRollProperty.get()); +} + +function setHeadPitchThreshold(num) { + headPitchProperty.value = num; + print("head pitch threshold " + headPitchProperty.get()); +} + +function setHeightThreshold(num) { + heightDifferenceProperty.value = num; + print("height threshold " + heightDifferenceProperty.get()); +} + +function setLateralDistance(num) { + lateralBaseProperty.value = num; + frontLeft.x = -lateralBaseProperty.get(); + frontRight.x = lateralBaseProperty.get(); + backLeft.x = -lateralBaseProperty.get(); + backRight.x = lateralBaseProperty.get(); + print("lateral distance " + lateralBaseProperty.get()); +} + +function setAnteriorDistance(num) { + frontBaseProperty.value = num; + frontLeft.z = -frontBaseProperty.get(); + frontRight.z = -frontBaseProperty.get(); + print("anterior distance " + frontBaseProperty.get()); +} + +function setPosteriorDistance(num) { + backBaseProperty.value = num; + backLeft.z = backBaseProperty.get(); + backRight.z = backBaseProperty.get(); + print("posterior distance " + backBaseProperty.get()); +} + +function setHandAngularVelocityThreshold(num) { + handsAngularVelocityProperty.value = num; + print("hand angular velocity threshold " + handsAngularVelocityProperty.get()); +} + +function setHandVelocityThreshold(num) { + handsVelocityProperty.value = num; + print("hand velocity threshold " + handsVelocityProperty.get()); +} + +function setHeadVelocityThreshold(num) { + headVelocityProperty.value = num; + print("headvelocity threshold " + headVelocityProperty.get()); +} + +function withinBaseOfSupport(pos) { + var userScale = 1.0; + frontBaseProperty.signalOn = !(isInsideLine(Vec3.multiply(userScale, frontLeft), Vec3.multiply(userScale, frontRight), pos)); + backBaseProperty.signalOn = !(isInsideLine(Vec3.multiply(userScale, backRight), Vec3.multiply(userScale, backLeft), pos)); + lateralBaseProperty.signalOn = !(isInsideLine(Vec3.multiply(userScale, frontRight), Vec3.multiply(userScale, backRight), pos) + && isInsideLine(Vec3.multiply(userScale, backLeft), Vec3.multiply(userScale, frontLeft), pos)); + return (!frontBaseProperty.signalOn && !backBaseProperty.signalOn && !lateralBaseProperty.signalOn); +} + +function withinThresholdOfStandingHeightMode(heightDiff) { + if (usingModeHeight.data.value) { + heightDifferenceProperty.signalOn = heightDiff < heightDifferenceProperty.get(); + return heightDifferenceProperty.signalOn; + } else { + return true; + } +} + +function headAngularVelocityBelowThreshold(headAngularVelocity) { + var angVel = Vec3.length({ x: headAngularVelocity.x, y: 0, z: headAngularVelocity.z }); + headAngularVelocityProperty.signalOn = angVel < headAngularVelocityProperty.get(); + return headAngularVelocityProperty.signalOn; +} + +function handDirectionMatchesHeadDirection(lhPose, rhPose) { + handsVelocityProperty.signalOn = ((handsVelocityProperty.get() < NO_SHARED_DIRECTION) || + ((!lhPose.valid || ((handDotHead[LEFT] > handsVelocityProperty.get()) && + (Vec3.length(lhPose.velocity) > VELOCITY_EPSILON))) && + (!rhPose.valid || ((handDotHead[RIGHT] > handsVelocityProperty.get()) && + (Vec3.length(rhPose.velocity) > VELOCITY_EPSILON))))); + return handsVelocityProperty.signalOn; +} + +function handAngularVelocityBelowThreshold(lhPose, rhPose) { + var xzRHandAngularVelocity = Vec3.length({ x: rhPose.angularVelocity.x, y: 0.0, z: rhPose.angularVelocity.z }); + var xzLHandAngularVelocity = Vec3.length({ x: lhPose.angularVelocity.x, y: 0.0, z: lhPose.angularVelocity.z }); + handsAngularVelocityProperty.signalOn = ((!rhPose.valid ||(xzRHandAngularVelocity < handsAngularVelocityProperty.get())) + && (!lhPose.valid || (xzLHandAngularVelocity < handsAngularVelocityProperty.get()))); + return handsAngularVelocityProperty.signalOn; +} + +function headVelocityGreaterThanThreshold(headVel) { + headVelocityProperty.signalOn = (headVel > headVelocityProperty.get()) || (headVelocityProperty.get() < VELOCITY_EPSILON); + return headVelocityProperty.signalOn; +} + +function headMovedAwayFromAveragePosition(headDelta) { + return !withinBaseOfSupport(headDelta) || !usingAverageHeadPosition.data.value; +} + +function headLowerThanHeightAverage(heightDiff) { + if (usingAverageHeight.data.value) { + print("head lower than height average"); + heightDifferenceProperty.signalOn = heightDiff < heightDifferenceProperty.get(); + return heightDifferenceProperty.signalOn; + } else { + return true; + } +} + +function isHeadLevel(diffEulers) { + headRollProperty.signalOn = Math.abs(diffEulers.z) < headRollProperty.get(); + headPitchProperty.signalOn = Math.abs(diffEulers.x) < headPitchProperty.get(); + return (headRollProperty.signalOn && headPitchProperty.signalOn); +} + +function findAverage(arr) { + var sum = arr.reduce(function (acc, val) { + return acc + val; + },0); + return sum / arr.length; +} + +function addToModeArray(arr,num) { + for (var i = 0 ;i < (arr.length - 1); i++) { + arr[i] = arr[i+1]; + } + arr[arr.length - 1] = (Math.floor(num*CENTIMETERSPERMETER))/CENTIMETERSPERMETER; +} + +function findMode(ary, currentMode, backLength, defaultBack, currentHeight) { + var numMapping = {}; + var greatestFreq = 0; + var mode; + ary.forEach(function (number) { + numMapping[number] = (numMapping[number] || 0) + 1; + if ((greatestFreq < numMapping[number]) || ((numMapping[number] === MODE_SAMPLE_LENGTH) && (number > currentMode) )) { + greatestFreq = numMapping[number]; + mode = number; + } + }); + if (mode > currentMode) { + return Number(mode); + } else { + if (!RESET_MODE && HMD.active) { + print("resetting the mode............................................. "); + print("resetting the mode............................................. "); + RESET_MODE = true; + var correction = 0.02; + return currentHeight - correction; + } else { + return currentMode; + } + } +} + +function update(dt) { + if (debugDrawBase) { + drawBase(); + } + // Update current state information + currentStateReadings.headPose = Controller.getPoseValue(Controller.Standard.Head); + currentStateReadings.rhandPose = Controller.getPoseValue(Controller.Standard.RightHand); + currentStateReadings.lhandPose = Controller.getPoseValue(Controller.Standard.LeftHand); + + // back length + var headMinusHipLean = Vec3.subtract(currentStateReadings.headPose.translation, DEFAULT_HIPS_POSITION); + currentStateReadings.backLength = Vec3.length(headMinusHipLean); + // print("back length and default " + currentStateReadings.backLength + " " + DEFAULT_TORSO_LENGTH); + + // mode height + addToModeArray(modeArray, currentStateReadings.headPose.translation.y); + modeHeight = findMode(modeArray, modeHeight, currentStateReadings.backLength, DEFAULT_TORSO_LENGTH, + currentStateReadings.headPose.translation.y); + currentStateReadings.diffFromMode = modeHeight - currentStateReadings.headPose.translation.y; + + // hand direction + var leftHandLateralPoseVelocity = currentStateReadings.lhandPose.velocity; + leftHandLateralPoseVelocity.y = 0.0; + var rightHandLateralPoseVelocity = currentStateReadings.rhandPose.velocity; + rightHandLateralPoseVelocity.y = 0.0; + var headLateralPoseVelocity = currentStateReadings.headPose.velocity; + headLateralPoseVelocity.y = 0.0; + handDotHead[LEFT] = Vec3.dot(Vec3.normalize(leftHandLateralPoseVelocity), Vec3.normalize(headLateralPoseVelocity)); + handDotHead[RIGHT] = Vec3.dot(Vec3.normalize(rightHandLateralPoseVelocity), Vec3.normalize(headLateralPoseVelocity)); + + // average head position + headAveragePosition = Vec3.mix(headAveragePosition, currentStateReadings.headPose.translation, AVERAGING_RATE); + currentStateReadings.diffFromAveragePosition = Vec3.subtract(currentStateReadings.headPose.translation, + headAveragePosition); + + // average height + averageHeight = currentStateReadings.headPose.translation.y * HEIGHT_AVERAGING_RATE + + averageHeight * (1.0 - HEIGHT_AVERAGING_RATE); + currentStateReadings.diffFromAverageHeight = Math.abs(currentStateReadings.headPose.translation.y - averageHeight); + + // eulers diff + headEulers = Quat.safeEulerAngles(currentStateReadings.headPose.rotation); + headAverageOrientation = Quat.slerp(headAverageOrientation, currentStateReadings.headPose.rotation, AVERAGING_RATE); + headAverageEulers = Quat.safeEulerAngles(headAverageOrientation); + currentStateReadings.diffFromAverageEulers = Vec3.subtract(headAverageEulers, headEulers); + + // headpose rig space is for determining when to recenter rotation. + var headPoseRigSpace = Quat.multiply(CHANGE_OF_BASIS_ROTATION, currentStateReadings.headPose.rotation); + headPoseAverageOrientation = Quat.slerp(headPoseAverageOrientation, headPoseRigSpace, AVERAGING_RATE); + var headPoseAverageEulers = Quat.safeEulerAngles(headPoseAverageOrientation); + + // make the signal colors reflect the current thresholds that have been crossed + updateSignalColors(); + print("the current back length is " + currentStateReadings.backLength + " " + DEFAULT_TORSO_LENGTH); + // Conditions for taking a step. + // 1. off the base of support. front, lateral, back edges. + // 2. head is not lower than the height mode value by more than the maxHeightChange tolerance + // 3. the angular velocity of the head is not greater than the threshold value + // ie this reflects the speed the head is rotating away from having up = (0,1,0) in Avatar frame.. + // 4. the hands velocity vector has the same direction as the head, within the given tolerance + // the tolerance is an acos value, -1 means the hands going in any direction will not block translating + // up to 1 where the hands velocity direction must exactly match that of the head. -1 threshold disables this condition. + // 5. the angular velocity xz magnitude for each hand is below the threshold value + // ie here this reflects the speed that each hand is rotating away from having up = (0,1,0) in Avatar frame. + // 6. head velocity is below step threshold + // 7. head has moved further than the threshold from the running average position of the head. + // 8. head height is not lower than the running average head height with a difference of maxHeightChange. + // 9. head's rotation in avatar space is not pitching or rolling greater than the pitch or roll thresholds + if (!withinBaseOfSupport(currentStateReadings.headPose.translation) && + withinThresholdOfStandingHeightMode(currentStateReadings.diffFromMode) && + headAngularVelocityBelowThreshold(currentStateReadings.headPose.angularVelocity) && + handDirectionMatchesHeadDirection(currentStateReadings.lhandPose, currentStateReadings.rhandPose) && + handAngularVelocityBelowThreshold(currentStateReadings.lhandPose, currentStateReadings.rhandPose) && + headVelocityGreaterThanThreshold(Vec3.length(currentStateReadings.headPose.velocity)) && + headMovedAwayFromAveragePosition(currentStateReadings.diffFromAveragePosition) && + headLowerThanHeightAverage(currentStateReadings.diffFromAverageHeight) && + isHeadLevel(currentStateReadings.diffFromAverageEulers)) { + + if (stepTimer < 0.0) { //!MyAvatar.isRecenteringHorizontally() + print("trigger recenter========================================================"); + MyAvatar.triggerHorizontalRecenter(); + stepTimer = STEP_TIME_SECS; + } + } else if ((currentStateReadings.backLength > (DEFAULT_TORSO_LENGTH + SPINE_STRETCH_LIMIT)) && + (failsafeSignalTimer < 0.0) && HMD.active) { + // do the failsafe recenter. + // failsafeFlag stops repeated setting of failsafe button color. + // RESET_MODE false forces a reset of the height + RESET_MODE = false; + failsafeFlag = true; + failsafeSignalTimer = FAILSAFE_TIMEOUT; + MyAvatar.triggerHorizontalRecenter(); + tablet.emitScriptEvent(JSON.stringify({ "type": "failsafe", "id": "failsafeSignal", "data": { "value": "green" } })); + // in fail safe we debug print the values that were blocking us. + print("failsafe debug---------------------------------------------------------------"); + propArray.forEach(function (prop) { + print(prop.name); + if (!prop.signalOn) { + print(prop.signalType + " contributed to failsafe call"); + } + }); + print("end failsafe debug---------------------------------------------------------------"); + + } + + if ((failsafeSignalTimer < 0.0) && failsafeFlag) { + failsafeFlag = false; + tablet.emitScriptEvent(JSON.stringify({ "type": "failsafe", "id": "failsafeSignal", "data": { "value": "orange" } })); + } + + stepTimer -= dt; + failsafeSignalTimer -= dt; + + if (!HMD.active) { + RESET_MODE = false; + } + + if (Math.abs(headPoseAverageEulers.y) > HEAD_TURN_THRESHOLD) { + // Turn feet + // MyAvatar.triggerRotationRecenter(); + // headPoseAverageOrientation = { x: 0, y: 0, z: 0, w: 1 }; + } +} + +function shutdownTabletApp() { + // GlobalDebugger.stop(); + tablet.removeButton(tabletButton); + if (activated) { + tablet.webEventReceived.disconnect(onWebEventReceived); + tablet.gotoHomeScreen(); + } + tablet.screenChanged.disconnect(onScreenChanged); +} + +tabletButton.clicked.connect(manageClick); +tablet.screenChanged.connect(onScreenChanged); + +Script.setTimeout(function() { + DEFAULT_HIPS_POSITION = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(MyAvatar.getJointIndex("Hips")); + DEFAULT_HEAD_POSITION = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(MyAvatar.getJointIndex("Head")); + DEFAULT_TORSO_LENGTH = Vec3.length(Vec3.subtract(DEFAULT_HEAD_POSITION, DEFAULT_HIPS_POSITION)); +},(4*LOADING_DELAY)); + +Script.update.connect(update); +Controller.keyPressEvent.connect(onKeyPress); +Script.scriptEnding.connect(function () { + MyAvatar.hmdLeanRecenterEnabled = true; + Script.update.disconnect(update); + shutdownTabletApp(); +}); From cd70c97e90f19101968bcae9b77df5aec9d1d1e1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 16 Aug 2018 10:58:14 +1200 Subject: [PATCH 042/744] Add tablet rezzer script to default scripts --- scripts/defaultScripts.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index b275660c0f..0e6aa08ba6 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -31,7 +31,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/dialTone.js", "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", - "system/emote.js" + "system/emote.js", + "system/tabletRezzer.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", From cb9b7f567a1bdae3338668469f546cf0c92cb147 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 16 Aug 2018 16:08:36 +1200 Subject: [PATCH 043/744] Move update functions into state machine --- scripts/system/tabletRezzer.js | 98 +++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 44 deletions(-) diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index b5f94a7c87..3033177b7e 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -121,6 +121,19 @@ } } + function updateProxyHidden() { + // Don't show proxy is tablet is already displayed or are in toolbar mode. + if (HMD.showTablet || tablet.toolbarMode) { + return; + } + // Compare palm directions of hands with vectors from palms to camera. + if (shouldShowProxy(LEFT_HAND)) { + setState(PROXY_VISIBLE, LEFT_HAND); + } else if (shouldShowProxy(RIGHT_HAND)) { + setState(PROXY_VISIBLE, RIGHT_HAND); + } + } + function enterProxyVisible(hand) { proxyHand = hand; proxyOverlay = Overlays.addOverlay("model", { @@ -138,6 +151,25 @@ }); } + function updateProxyVisible() { + // Hide proxy if tablet has been displayed by other means. + if (HMD.showTablet) { + setState(PROXY_HIDDEN); + return; + } + // Check that palm direction of proxy hand still less than maximum angle. + if (!shouldShowProxy(proxyHand)) { + setState(PROXY_HIDDEN); + } + } + + function updateProxyGrabbed() { + // Hide proxy if tablet has been displayed by other means. + if (HMD.showTablet) { + setState(PROXY_HIDDEN); + } + } + function expandProxy() { var scaleFactor = (Date.now() - proxyExpandStart) / PROXY_EXPAND_DURATION; if (scaleFactor < 1) { @@ -168,6 +200,13 @@ proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); } + function updateProxyExanding() { + // Hide proxy if tablet has been displayed by other means. + if (HMD.showTablet) { + setState(PROXY_HIDDEN); + } + } + function exitProxyExpanding() { if (proxyExpandTimer !== null) { Script.clearTimeout(proxyExpandTimer); @@ -188,30 +227,35 @@ HMD.openTablet(true); } + function updateTabletOpen() { + // Immediately transition back to PROXY_HIDDEN. + setState(PROXY_HIDDEN); + } + STATE_MACHINE = { PROXY_HIDDEN: { // Tablet proxy could be shown but isn't because hand is oriented to show it or aren't in HMD mode. enter: enterProxyHidden, - update: null, + update: updateProxyHidden, exit: null }, PROXY_VISIBLE: { // Tablet proxy is visible and attached to hand. enter: enterProxyVisible, - update: null, + update: updateProxyVisible, exit: null }, PROXY_GRABBED: { // Other hand has grabbed and is holding the tablet proxy. enter: null, - update: null, + update: updateProxyGrabbed, exit: null }, PROXY_EXPANDING: { // Tablet proxy has been released from grab and is expanding before showing tablet proper. enter: enterProxyExpanding, - update: null, + update: updateProxyExanding, exit: exitProxyExpanding }, TABLET_OPEN: { // Tablet proper is being displayed. enter: enterTabletOpen, - update: null, + update: updateTabletOpen, exit: null } }; @@ -231,6 +275,10 @@ } } + function updateState() { + STATE_MACHINE[STATE_STRINGS[rezzerState]].update(); + } + // #endregion // #region Events ========================================================================================================== @@ -258,45 +306,7 @@ function update() { // Assumes that is HMD.mounted. - switch (rezzerState) { - case PROXY_HIDDEN: - // Don't show proxy is tablet is already displayed or are in toolbar mode. - if (HMD.showTablet || tablet.toolbarMode) { - break; - } - // Compare palm directions of hands with vectors from palms to camera. - if (shouldShowProxy(LEFT_HAND)) { - setState(PROXY_VISIBLE, LEFT_HAND); - } else if (shouldShowProxy(RIGHT_HAND)) { - setState(PROXY_VISIBLE, RIGHT_HAND); - } - break; - case PROXY_VISIBLE: - // Hide proxy if tablet has been displayed by other means. - if (HMD.showTablet) { - setState(PROXY_HIDDEN); - break; - } - // Check that palm direction of proxy hand still less than maximum angle. - if (!shouldShowProxy(proxyHand)) { - setState(PROXY_HIDDEN); - } - break; - case PROXY_GRABBED: - case PROXY_EXPANDING: - // Hide proxy if tablet has been displayed by other means. - if (HMD.showTablet) { - setState(PROXY_HIDDEN); - } - break; - case TABLET_OPEN: - // Immediately transition back to PROXY_HIDDEN. - setState(PROXY_HIDDEN); - break; - default: - error("Missing case: " + rezzerState); - } - + updateState(); updateTimer = Script.setTimeout(update, UPDATE_INTERVAL); } From 7a1a86f1824e57b2d44340be5f5b05e1b7db1a10 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 16 Aug 2018 16:20:28 +1200 Subject: [PATCH 044/744] Expand proxy tablet immediately it is grabbed; grab with either hand --- scripts/system/tabletRezzer.js | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index 3033177b7e..c261ebb059 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -42,10 +42,9 @@ // State machine PROXY_HIDDEN = 0, PROXY_VISIBLE = 1, - PROXY_GRABBED = 2, - PROXY_EXPANDING = 3, - TABLET_OPEN = 4, - STATE_STRINGS = ["PROXY_HIDDEN", "PROXY_VISIBLE", "PROXY_GRABBED", "PROXY_EXPANDING", "TABLET_OPEN"], + PROXY_EXPANDING = 2, + TABLET_OPEN = 3, + STATE_STRINGS = ["PROXY_HIDDEN", "PROXY_VISIBLE", "PROXY_EXPANDING", "TABLET_OPEN"], STATE_MACHINE, rezzerState = PROXY_HIDDEN, proxyHand, @@ -163,13 +162,6 @@ } } - function updateProxyGrabbed() { - // Hide proxy if tablet has been displayed by other means. - if (HMD.showTablet) { - setState(PROXY_HIDDEN); - } - } - function expandProxy() { var scaleFactor = (Date.now() - proxyExpandStart) / PROXY_EXPAND_DURATION; if (scaleFactor < 1) { @@ -243,12 +235,7 @@ update: updateProxyVisible, exit: null }, - PROXY_GRABBED: { // Other hand has grabbed and is holding the tablet proxy. - enter: null, - update: updateProxyGrabbed, - exit: null - }, - PROXY_EXPANDING: { // Tablet proxy has been released from grab and is expanding before showing tablet proper. + PROXY_EXPANDING: { // Tablet proxy has been grabbed and is expanding before showing tablet proper. enter: enterProxyExpanding, update: updateProxyExanding, exit: exitProxyExpanding @@ -328,11 +315,7 @@ return; } - // Don't transition into PROXY_GRABBED unless the tablet proxy is grabbed with "other" hand. However, once it has been - // grabbed then the original hand can grab it back and grow it from there. - if (message.action === "grab" && message.joint !== HAND_NAMES[proxyHand] && rezzerState === PROXY_VISIBLE) { - setState(PROXY_GRABBED); - } else if (message.action === "release" && rezzerState === PROXY_GRABBED) { + if (message.action === "grab" && rezzerState === PROXY_VISIBLE) { setState(PROXY_EXPANDING); } } From 40f7914aae987394397956ce86d95d1e10875b09 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 16 Aug 2018 19:11:03 +1200 Subject: [PATCH 045/744] Expand from lower left/right of proxy tablet --- scripts/system/tabletRezzer.js | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index c261ebb059..1779defb28 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -48,6 +48,13 @@ STATE_MACHINE, rezzerState = PROXY_HIDDEN, proxyHand, + PROXY_GRAB_HANDLES = [ + { x: 0.5, y: -0.4, z: 0 }, + { x: -0.5, y: -0.4, z: 0 } + ], + proxyGrabHand, + proxyGrabHandleLocalPosition, + proxyGrabLocalRotation = Quat.IDENTITY, PROXY_EXPAND_DURATION = 250, PROXY_EXPAND_TIMEOUT = 25, proxyExpandTimer = null, @@ -109,6 +116,10 @@ return MyAvatar.getJointIndex(handJointName(hand)); } + function otherHand(hand) { + return hand === LEFT_HAND ? RIGHT_HAND : LEFT_HAND; + } + // #endregion // #region State Machine =================================================================================================== @@ -164,11 +175,16 @@ function expandProxy() { var scaleFactor = (Date.now() - proxyExpandStart) / PROXY_EXPAND_DURATION; + var tabletScaleFactor = avatarScale * (1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth); if (scaleFactor < 1) { Overlays.editOverlay(proxyOverlay, { - dimensions: Vec3.multiply( - avatarScale * (1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth), - TABLET_PROXY_DIMENSIONS) + dimensions: Vec3.multiply(tabletScaleFactor, TABLET_PROXY_DIMENSIONS), + localPosition: + Vec3.sum(proxyGrabHandleLocalPosition, + Vec3.multiplyQbyV(proxyGrabLocalRotation, + Vec3.multiply(-tabletScaleFactor, + Vec3.multiplyVbyV(PROXY_GRAB_HANDLES[proxyGrabHand], TABLET_PROXY_DIMENSIONS))) + ) }); proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); return; @@ -179,11 +195,12 @@ } function enterProxyExpanding() { - // Detach from hand. - Overlays.editOverlay(proxyOverlay, { - parentID: Uuid.NULL, - parentJointIndex: -1 - }); + // Grab details. + var properties = Overlays.getProperties(proxyOverlay, ["localPosition", "localRotation"]); + proxyGrabLocalRotation = properties.localRotation; + proxyGrabHandleLocalPosition = Vec3.sum(properties.localPosition, + Vec3.multiplyQbyV(proxyGrabLocalRotation, + Vec3.multiplyVbyV(PROXY_GRAB_HANDLES[proxyGrabHand], TABLET_PROXY_DIMENSIONS))); // Start expanding. proxyInitialWidth = TABLET_PROXY_DIMENSIONS.x; @@ -316,6 +333,7 @@ } if (message.action === "grab" && rezzerState === PROXY_VISIBLE) { + proxyGrabHand = message.joint === HAND_NAMES[proxyHand] ? proxyHand : otherHand(proxyHand); setState(PROXY_EXPANDING); } } From 2c389af751d4710c1556eeb7e1183783b4f0b156 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 17 Aug 2018 16:18:22 +1200 Subject: [PATCH 046/744] Display mini tablet only if camera is also looking at the hand --- scripts/system/tabletRezzer.js | 44 ++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index 1779defb28..8fe206e9e0 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -124,6 +124,29 @@ // #region State Machine =================================================================================================== + function shouldShowProxy(hand) { + // Should show tablet proxy if hand is oriented toward the camera and the camera is oriented toward the proxy tablet. + var pose, + jointIndex, + handPosition, + handOrientation, + cameraToHandDirection; + + pose = Controller.getPoseValue(hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand); + if (!pose.valid) { + return false; + } + + jointIndex = handJointIndex(hand); + handPosition = Vec3.sum(MyAvatar.position, + Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex))); + handOrientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex)); + cameraToHandDirection = Vec3.normalize(Vec3.subtract(handPosition, Camera.position)); + + return Vec3.dot(cameraToHandDirection, Quat.getForward(handOrientation)) > MIN_HAND_CAMERA_ANGLE_COS + && Vec3.dot(cameraToHandDirection, Quat.getForward(Camera.orientation)) > MIN_HAND_CAMERA_ANGLE_COS; + } + function enterProxyHidden() { if (proxyOverlay) { Overlays.deleteOverlay(proxyOverlay); @@ -287,27 +310,6 @@ // #region Events ========================================================================================================== - function shouldShowProxy(hand) { - // Should show tablet proxy if hand is oriented towards the camera. - var pose, - jointIndex, - handPosition, - handOrientation; - - pose = Controller.getPoseValue(hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand); - if (!pose.valid) { - return false; - } - - jointIndex = handJointIndex(hand); - handPosition = Vec3.sum(MyAvatar.position, - Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex))); - handOrientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex)); - - return Vec3.dot(Vec3.normalize(Vec3.subtract(handPosition, Camera.position)), Quat.getForward(handOrientation)) - > MIN_HAND_CAMERA_ANGLE_COS; - } - function update() { // Assumes that is HMD.mounted. updateState(); From 9f289711cee9a850b643278b2c23da76a6c36665 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 17 Aug 2018 14:33:21 -0700 Subject: [PATCH 047/744] fixing scripting window visiblity and size issues --- scripts/developer/debugging/debugWindow.js | 25 ++++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/scripts/developer/debugging/debugWindow.js b/scripts/developer/debugging/debugWindow.js index 9522676007..993ca49a40 100644 --- a/scripts/developer/debugging/debugWindow.js +++ b/scripts/developer/debugging/debugWindow.js @@ -23,7 +23,7 @@ if (scripts.length >= 2) { var qml = Script.resolvePath('debugWindow.qml'); var HMD_DEBUG_WINDOW_GEOMETRY_KEY = 'hmdDebugWindowGeometry'; -var hmdDebugWindowGeometryValue = Settings.getValue(HMD_DEBUG_WINDOW_GEOMETRY_KEY) +var hmdDebugWindowGeometryValue = Settings.getValue(HMD_DEBUG_WINDOW_GEOMETRY_KEY); var windowWidth = 400; var windowHeight = 900; @@ -34,12 +34,13 @@ var windowY = 0; if (hmdDebugWindowGeometryValue !== '') { var geometry = JSON.parse(hmdDebugWindowGeometryValue); - - windowWidth = geometry.width - windowHeight = geometry.height - windowX = geometry.x - windowY = geometry.y - hasPosition = true; + if ((geometry.x !== 0) && (geometry.y !== 0)) { + windowWidth = geometry.width; + windowHeight = geometry.height; + windowX = geometry.x; + windowY = geometry.y; + hasPosition = true; + } } var window = new OverlayWindow({ @@ -52,6 +53,12 @@ if (hasPosition) { window.setPosition(windowX, windowY); } +window.visibleChanged.connect(function() { + if (!window.visible) { + window.setVisible(true); + } +}); + window.closed.connect(function () { Script.stop(); }); var getFormattedDate = function() { @@ -93,10 +100,10 @@ Script.scriptEnding.connect(function () { y: window.position.y, width: window.size.x, height: window.size.y - }) + }); Settings.setValue(HMD_DEBUG_WINDOW_GEOMETRY_KEY, geometry); window.close(); -}) +}); }()); From 94041291d5c7e66b10ffac93d7b7a0cec39988b5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 20 Aug 2018 10:58:01 +1200 Subject: [PATCH 048/744] Add web3d overlay for UI surface --- scripts/system/html/tabletRezzer.html | 21 +++++++++ scripts/system/tabletRezzer.js | 66 ++++++++++++++++++++------- 2 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 scripts/system/html/tabletRezzer.html diff --git a/scripts/system/html/tabletRezzer.html b/scripts/system/html/tabletRezzer.html new file mode 100644 index 0000000000..6a0e0d0448 --- /dev/null +++ b/scripts/system/html/tabletRezzer.html @@ -0,0 +1,21 @@ + + + + + + + + + + + Hello world! + + diff --git a/scripts/system/tabletRezzer.js b/scripts/system/tabletRezzer.js index 8fe206e9e0..fe4bc6477b 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/tabletRezzer.js @@ -16,28 +16,39 @@ Script.include("./libraries/utils.js"); - var // Overlay + var // Base overlay proxyOverlay = null, - TABLET_PROXY_MODEL = Script.resolvePath("./assets/models/tinyTablet.fbx"), - TABLET_PROXY_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0046 }, // Proportional to tablet proper. - TABLET_PROXY_POSITION_LEFT_HAND = { + PROXY_MODEL = Script.resolvePath("./assets/models/tinyTablet.fbx"), + PROXY_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0046 }, // Proportional to tablet proper. + PROXY_POSITION_LEFT_HAND = { x: 0, y: 0.07, // Distance from joint. z: 0.07 // Distance above palm. }, - TABLET_PROXY_POSITION_RIGHT_HAND = { + PROXY_POSITION_RIGHT_HAND = { x: 0, y: 0.07, // Distance from joint. z: 0.07 // Distance above palm. }, /* // Aligned cross-palm. - TABLET_PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 90 }), - TABLET_PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: -90 }), + PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 90 }), + PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: -90 }), */ // Aligned with palm. - TABLET_PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), - TABLET_PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + + // UI overlay. + proxyUIOverlay = null, + PROXY_UI_HTML = Script.resolvePath("./html/tabletRezzer.html"), + PROXY_UI_DIMENSIONS = { x: 0.0577, y: 0.0905 }, + PROXY_UI_WIDTH_PIXELS = 150, + METERS_TO_INCHES = 39.3701, + PROXY_UI_DPI = PROXY_UI_WIDTH_PIXELS / (PROXY_UI_DIMENSIONS.x * METERS_TO_INCHES), + PROXY_UI_OFFSET = 0.001, // Above model surface. + PROXY_UI_LOCAL_POSITION = { x: 0, y: 0, z: -(PROXY_DIMENSIONS.z / 2 + PROXY_UI_OFFSET) }, + PROXY_UI_LOCAL_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), // State machine PROXY_HIDDEN = 0, @@ -149,7 +160,9 @@ function enterProxyHidden() { if (proxyOverlay) { + Overlays.deleteOverlay(proxyUIOverlay); Overlays.deleteOverlay(proxyOverlay); + proxyUIOverlay = null; proxyOverlay = null; } } @@ -170,18 +183,30 @@ function enterProxyVisible(hand) { proxyHand = hand; proxyOverlay = Overlays.addOverlay("model", { - url: TABLET_PROXY_MODEL, + url: PROXY_MODEL, parentID: MyAvatar.SELF_ID, parentJointIndex: handJointIndex(proxyHand), localPosition: Vec3.multiply(avatarScale, - proxyHand === LEFT_HAND ? TABLET_PROXY_POSITION_LEFT_HAND : TABLET_PROXY_POSITION_RIGHT_HAND), - localRotation: proxyHand === LEFT_HAND ? TABLET_PROXY_ROTATION_LEFT_HAND : TABLET_PROXY_ROTATION_RIGHT_HAND, - dimensions: Vec3.multiply(avatarScale, TABLET_PROXY_DIMENSIONS), + proxyHand === LEFT_HAND ? PROXY_POSITION_LEFT_HAND : PROXY_POSITION_RIGHT_HAND), + localRotation: proxyHand === LEFT_HAND ? PROXY_ROTATION_LEFT_HAND : PROXY_ROTATION_RIGHT_HAND, + dimensions: Vec3.multiply(avatarScale, PROXY_DIMENSIONS), solid: true, grabbable: true, displayInFront: true, visible: true }); + proxyUIOverlay = Overlays.addOverlay("web3d", { + url: PROXY_UI_HTML, + parentID: proxyOverlay, + localPosition: PROXY_UI_LOCAL_POSITION, + localRotation: PROXY_UI_LOCAL_ROTATION, + dimensions: PROXY_UI_DIMENSIONS, + dpi: PROXY_UI_DPI, + alpha: 1.0, + grabbable: false, + displayInFront: true, + visible: true + }); } function updateProxyVisible() { @@ -201,14 +226,19 @@ var tabletScaleFactor = avatarScale * (1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth); if (scaleFactor < 1) { Overlays.editOverlay(proxyOverlay, { - dimensions: Vec3.multiply(tabletScaleFactor, TABLET_PROXY_DIMENSIONS), + dimensions: Vec3.multiply(tabletScaleFactor, PROXY_DIMENSIONS), localPosition: Vec3.sum(proxyGrabHandleLocalPosition, Vec3.multiplyQbyV(proxyGrabLocalRotation, Vec3.multiply(-tabletScaleFactor, - Vec3.multiplyVbyV(PROXY_GRAB_HANDLES[proxyGrabHand], TABLET_PROXY_DIMENSIONS))) + Vec3.multiplyVbyV(PROXY_GRAB_HANDLES[proxyGrabHand], PROXY_DIMENSIONS))) ) }); + Overlays.editOverlay(proxyUIOverlay, { + dimensions: Vec3.multiply(tabletScaleFactor, PROXY_UI_DIMENSIONS), + localPosition: Vec3.multiply(tabletScaleFactor, PROXY_UI_LOCAL_POSITION), + dpi: PROXY_UI_DPI / tabletScaleFactor + }); proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); return; } @@ -223,10 +253,10 @@ proxyGrabLocalRotation = properties.localRotation; proxyGrabHandleLocalPosition = Vec3.sum(properties.localPosition, Vec3.multiplyQbyV(proxyGrabLocalRotation, - Vec3.multiplyVbyV(PROXY_GRAB_HANDLES[proxyGrabHand], TABLET_PROXY_DIMENSIONS))); + Vec3.multiplyVbyV(PROXY_GRAB_HANDLES[proxyGrabHand], PROXY_DIMENSIONS))); // Start expanding. - proxyInitialWidth = TABLET_PROXY_DIMENSIONS.x; + proxyInitialWidth = PROXY_DIMENSIONS.x; proxyTargetWidth = getTabletWidthFromSettings(); proxyExpandStart = Date.now(); proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); @@ -249,6 +279,8 @@ function enterTabletOpen() { var proxyOverlayProperties = Overlays.getProperties(proxyOverlay, ["position", "orientation"]); + Overlays.deleteOverlay(proxyUIOverlay); + proxyUIOverlay = null; Overlays.deleteOverlay(proxyOverlay); proxyOverlay = null; From aca343330fb203d861a30e8b5c1a943df58b3dd4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 20 Aug 2018 16:00:02 +1200 Subject: [PATCH 049/744] Base UI elements --- scripts/defaultScripts.js | 2 +- scripts/system/html/css/miniTablet.css | 102 ++++++++++++++++++ scripts/system/html/miniTablet.html | 31 ++++++ scripts/system/html/tabletRezzer.html | 21 ---- .../system/{tabletRezzer.js => miniTablet.js} | 4 +- 5 files changed, 136 insertions(+), 24 deletions(-) create mode 100644 scripts/system/html/css/miniTablet.css create mode 100644 scripts/system/html/miniTablet.html delete mode 100644 scripts/system/html/tabletRezzer.html rename scripts/system/{tabletRezzer.js => miniTablet.js} (99%) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 0e6aa08ba6..2aee26f71e 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/emote.js", - "system/tabletRezzer.js" + "system/miniTablet.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/system/html/css/miniTablet.css b/scripts/system/html/css/miniTablet.css new file mode 100644 index 0000000000..d0585e489a --- /dev/null +++ b/scripts/system/html/css/miniTablet.css @@ -0,0 +1,102 @@ +/* +miniTablet.css + +Created by David Rowe on 20 Aug 2018. +Copyright 2018 High Fidelity, Inc. + +Distributed under the Apache License, Version 2.0. +See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +* { + box-sizing: border-box; + padding: 0; + margin: 0; + user-select: none; +} + +html { + background-color: #121212; +} + +body { + height: 100%; +} + +section { + background-color: #404040; + position: relative; + padding: 15px; +} + +section .button { + width: 100%; + height: 88px; + background-color: #252525; + margin-top: 10px; + text-align: center; + border-radius: 8px; +} + + section .button.off { + border: 2px solid #6a6a6a; + background-color: #303030; + } + + section .button.off:hover { + border: 2px solid #1fc6a6; + } + + section .button.on { + border: 2px solid #1fc6a6; + background-color: #1fc6a6; + } + + section .button.on:hover { + border: 2px solid #e3e3e3; + } + + section .button:first-child { + margin-top: 0; + } + +img { + width: 60px; +} + +#mute { + padding-top: 12px; +} + +#bubble { + padding-top: 14px; +} + +footer { + position: absolute; + bottom: 0; + width: 100%; + height: 30px; + text-align: center; + color: #e3e3e3; + font-weight: bold; +} + + footer div { + display: inline-block; + height: 12px; + width: 24px; + position: relative; + top: 16px; + } + + footer div p { + position: relative; + top: -8px; + } + + footer .button:hover { + border: 1px solid #1fc6a6; + border-radius: 2px; + margin: -1px; + } \ No newline at end of file diff --git a/scripts/system/html/miniTablet.html b/scripts/system/html/miniTablet.html new file mode 100644 index 0000000000..f8216e5515 --- /dev/null +++ b/scripts/system/html/miniTablet.html @@ -0,0 +1,31 @@ + + + + + + + + + + +
+
+ +
+
+ +
+
+
+

...

+
+ + diff --git a/scripts/system/html/tabletRezzer.html b/scripts/system/html/tabletRezzer.html deleted file mode 100644 index 6a0e0d0448..0000000000 --- a/scripts/system/html/tabletRezzer.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - Hello world! - - diff --git a/scripts/system/tabletRezzer.js b/scripts/system/miniTablet.js similarity index 99% rename from scripts/system/tabletRezzer.js rename to scripts/system/miniTablet.js index fe4bc6477b..aa8b96665b 100644 --- a/scripts/system/tabletRezzer.js +++ b/scripts/system/miniTablet.js @@ -1,5 +1,5 @@ // -// tabletRezzer.js +// miniTablet.js // // Created by David Rowe on 9 Aug 2018. // Copyright 2018 High Fidelity, Inc. @@ -41,7 +41,7 @@ // UI overlay. proxyUIOverlay = null, - PROXY_UI_HTML = Script.resolvePath("./html/tabletRezzer.html"), + PROXY_UI_HTML = Script.resolvePath("./html/miniTablet.html"), PROXY_UI_DIMENSIONS = { x: 0.0577, y: 0.0905 }, PROXY_UI_WIDTH_PIXELS = 150, METERS_TO_INCHES = 39.3701, From ff54516790fc6946d51810a0888207d15eb881ca Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 20 Aug 2018 17:46:07 +1200 Subject: [PATCH 050/744] Maintain overlays ready for display in HMD mode --- scripts/system/miniTablet.js | 225 ++++++++++++++++++++++------------- 1 file changed, 140 insertions(+), 85 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index aa8b96665b..f0741582c3 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -49,15 +49,18 @@ PROXY_UI_OFFSET = 0.001, // Above model surface. PROXY_UI_LOCAL_POSITION = { x: 0, y: 0, z: -(PROXY_DIMENSIONS.z / 2 + PROXY_UI_OFFSET) }, PROXY_UI_LOCAL_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + proxyUIOverlayEnabled = false, + PROXY_UI_OVERLAY_ENABLED_DELAY = 500, // State machine - PROXY_HIDDEN = 0, - PROXY_VISIBLE = 1, - PROXY_EXPANDING = 2, - TABLET_OPEN = 3, - STATE_STRINGS = ["PROXY_HIDDEN", "PROXY_VISIBLE", "PROXY_EXPANDING", "TABLET_OPEN"], + PROXY_DISABLED = 0, + PROXY_HIDDEN = 1, + PROXY_VISIBLE = 2, + PROXY_EXPANDING = 3, + TABLET_OPEN = 4, + STATE_STRINGS = ["PROXY_DISABLED", "PROXY_HIDDEN", "PROXY_VISIBLE", "PROXY_EXPANDING", "TABLET_OPEN"], STATE_MACHINE, - rezzerState = PROXY_HIDDEN, + rezzerState = PROXY_DISABLED, proxyHand, PROXY_GRAB_HANDLES = [ { x: 0.5, y: -0.4, z: 0 }, @@ -86,7 +89,7 @@ LEFT_HAND = 0, RIGHT_HAND = 1, HAND_NAMES = ["LeftHand", "RightHand"], - DEBUG = false; + DEBUG = true; // #region Utilities ======================================================================================================= @@ -133,8 +136,117 @@ // #endregion + // #region UI ============================================================================================================== + + function createUI() { + proxyOverlay = Overlays.addOverlay("model", { + url: PROXY_MODEL, + dimensions: Vec3.multiply(avatarScale, PROXY_DIMENSIONS), + solid: true, + grabbable: true, + displayInFront: true, + visible: false + }); + proxyUIOverlay = Overlays.addOverlay("web3d", { + url: PROXY_UI_HTML, + parentID: proxyOverlay, + localPosition: Vec3.multiply(avatarScale, PROXY_UI_LOCAL_POSITION), + localRotation: PROXY_UI_LOCAL_ROTATION, + dimensions: Vec3.multiply(avatarScale, PROXY_UI_DIMENSIONS), + dpi: PROXY_UI_DPI / avatarScale, + alpha: 0, // Hide overlay while its content is being created. + grabbable: false, + displayInFront: true, + visible: false + }); + + proxyUIOverlayEnabled = false; // This and alpha = 0 hides overlay while its content is being created. + } + + function showUI(hand) { + Overlays.editOverlay(proxyOverlay, { + parentID: MyAvatar.SELF_ID, + parentJointIndex: handJointIndex(proxyHand), + localPosition: Vec3.multiply(avatarScale, + proxyHand === LEFT_HAND ? PROXY_POSITION_LEFT_HAND : PROXY_POSITION_RIGHT_HAND), + localRotation: proxyHand === LEFT_HAND ? PROXY_ROTATION_LEFT_HAND : PROXY_ROTATION_RIGHT_HAND, + dimensions: Vec3.multiply(avatarScale, PROXY_DIMENSIONS), + visible: true + }); + Overlays.editOverlay(proxyUIOverlay, { + localPosition: Vec3.multiply(avatarScale, PROXY_UI_LOCAL_POSITION), + dimensions: Vec3.multiply(avatarScale, PROXY_UI_DIMENSIONS), + dpi: PROXY_UI_DPI / avatarScale, + visible: true + }); + + if (!proxyUIOverlayEnabled) { + // Overlay content is created the first time it is visible to the user. The initial creation displays artefacts. + // Delay showing UI overlay until after giving it time for its content to be created. + Script.setTimeout(function () { + Overlays.editOverlay(proxyUIOverlay, { alpha: 1.0 }); + }, PROXY_UI_OVERLAY_ENABLED_DELAY); + } + } + + function sizeUI(scaleFactor) { + Overlays.editOverlay(proxyOverlay, { + localPosition: + Vec3.sum(proxyGrabHandleLocalPosition, + Vec3.multiplyQbyV(proxyGrabLocalRotation, + Vec3.multiply(-scaleFactor, + Vec3.multiplyVbyV(PROXY_GRAB_HANDLES[proxyGrabHand], PROXY_DIMENSIONS))) + ), + dimensions: Vec3.multiply(scaleFactor, PROXY_DIMENSIONS) + }); + Overlays.editOverlay(proxyUIOverlay, { + localPosition: Vec3.multiply(scaleFactor, PROXY_UI_LOCAL_POSITION), + dimensions: Vec3.multiply(scaleFactor, PROXY_UI_DIMENSIONS), + dpi: PROXY_UI_DPI / scaleFactor + }); + } + + function hideUI() { + Overlays.editOverlay(proxyOverlay, { + visible: false + }); + Overlays.editOverlay(proxyUIOverlay, { + visible: false + }); + } + + function destroyUI() { + if (proxyOverlay) { + Overlays.deleteOverlay(proxyUIOverlay); + Overlays.deleteOverlay(proxyOverlay); + proxyUIOverlay = null; + proxyOverlay = null; + } + } + + // #endregion + // #region State Machine =================================================================================================== + function enterProxyDisabled() { + // Stop updates. + if (updateTimer !== null) { + Script.clearTimeout(updateTimer); + updateTimer = null; + } + + // Don't keep overlays prepared if in desktop mode. + destroyUI(); + } + + function exitProxyDisabled() { + // Create UI so that it's ready to be displayed without seeing artefacts from creating the UI. + createUI(); + + // Start updates. + updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL); + } + function shouldShowProxy(hand) { // Should show tablet proxy if hand is oriented toward the camera and the camera is oriented toward the proxy tablet. var pose, @@ -159,16 +271,11 @@ } function enterProxyHidden() { - if (proxyOverlay) { - Overlays.deleteOverlay(proxyUIOverlay); - Overlays.deleteOverlay(proxyOverlay); - proxyUIOverlay = null; - proxyOverlay = null; - } + hideUI(); } function updateProxyHidden() { - // Don't show proxy is tablet is already displayed or are in toolbar mode. + // Don't show proxy if tablet is already displayed or in toolbar mode. if (HMD.showTablet || tablet.toolbarMode) { return; } @@ -182,31 +289,7 @@ function enterProxyVisible(hand) { proxyHand = hand; - proxyOverlay = Overlays.addOverlay("model", { - url: PROXY_MODEL, - parentID: MyAvatar.SELF_ID, - parentJointIndex: handJointIndex(proxyHand), - localPosition: Vec3.multiply(avatarScale, - proxyHand === LEFT_HAND ? PROXY_POSITION_LEFT_HAND : PROXY_POSITION_RIGHT_HAND), - localRotation: proxyHand === LEFT_HAND ? PROXY_ROTATION_LEFT_HAND : PROXY_ROTATION_RIGHT_HAND, - dimensions: Vec3.multiply(avatarScale, PROXY_DIMENSIONS), - solid: true, - grabbable: true, - displayInFront: true, - visible: true - }); - proxyUIOverlay = Overlays.addOverlay("web3d", { - url: PROXY_UI_HTML, - parentID: proxyOverlay, - localPosition: PROXY_UI_LOCAL_POSITION, - localRotation: PROXY_UI_LOCAL_ROTATION, - dimensions: PROXY_UI_DIMENSIONS, - dpi: PROXY_UI_DPI, - alpha: 1.0, - grabbable: false, - displayInFront: true, - visible: true - }); + showUI(proxyHand); } function updateProxyVisible() { @@ -225,20 +308,7 @@ var scaleFactor = (Date.now() - proxyExpandStart) / PROXY_EXPAND_DURATION; var tabletScaleFactor = avatarScale * (1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth); if (scaleFactor < 1) { - Overlays.editOverlay(proxyOverlay, { - dimensions: Vec3.multiply(tabletScaleFactor, PROXY_DIMENSIONS), - localPosition: - Vec3.sum(proxyGrabHandleLocalPosition, - Vec3.multiplyQbyV(proxyGrabLocalRotation, - Vec3.multiply(-tabletScaleFactor, - Vec3.multiplyVbyV(PROXY_GRAB_HANDLES[proxyGrabHand], PROXY_DIMENSIONS))) - ) - }); - Overlays.editOverlay(proxyUIOverlay, { - dimensions: Vec3.multiply(tabletScaleFactor, PROXY_UI_DIMENSIONS), - localPosition: Vec3.multiply(tabletScaleFactor, PROXY_UI_LOCAL_POSITION), - dpi: PROXY_UI_DPI / tabletScaleFactor - }); + sizeUI(tabletScaleFactor); proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); return; } @@ -279,10 +349,7 @@ function enterTabletOpen() { var proxyOverlayProperties = Overlays.getProperties(proxyOverlay, ["position", "orientation"]); - Overlays.deleteOverlay(proxyUIOverlay); - proxyUIOverlay = null; - Overlays.deleteOverlay(proxyOverlay); - proxyOverlay = null; + hideUI(); Overlays.editOverlay(HMD.tabletID, { position: proxyOverlayProperties.position, @@ -297,6 +364,11 @@ } STATE_MACHINE = { + PROXY_DISABLED: { // Tablet proxy cannot be shown because in desktop mode. + enter: enterProxyDisabled, + update: null, + exit: exitProxyDisabled + }, PROXY_HIDDEN: { // Tablet proxy could be shown but isn't because hand is oriented to show it or aren't in HMD mode. enter: enterProxyHidden, update: updateProxyHidden, @@ -336,18 +408,13 @@ function updateState() { STATE_MACHINE[STATE_STRINGS[rezzerState]].update(); + updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL); } // #endregion // #region Events ========================================================================================================== - function update() { - // Assumes that is HMD.mounted. - updateState(); - updateTimer = Script.setTimeout(update, UPDATE_INTERVAL); - } - function onScaleChanged() { avatarScale = MyAvatar.scale; // Clamp scale in order to work around M17434. @@ -372,22 +439,12 @@ } } - function onMountedChanged() { - // Tablet proxy only available when HMD is mounted. - - if (HMD.mounted) { - if (updateTimer === null) { - update(); - } - return; - } - - if (updateTimer !== null) { - Script.clearTimeout(updateTimer); - updateTimer = null; - } - if (rezzerState !== PROXY_HIDDEN) { + function onDisplayModeChanged() { + // Tablet proxy only available when HMD is active. + if (HMD.active) { setState(PROXY_HIDDEN); + } else { + setState(PROXY_DISABLED); } } @@ -401,10 +458,9 @@ Messages.subscribe(HIFI_OBJECT_MANIPULATION_CHANNEL); Messages.messageReceived.connect(onMessageReceived); - HMD.mountedChanged.connect(onMountedChanged); - HMD.displayModeChanged.connect(onMountedChanged); // For the case that the HMD is already worn when the script starts. - if (HMD.mounted) { - update(); + HMD.displayModeChanged.connect(onDisplayModeChanged); + if (HMD.active) { + setState(PROXY_HIDDEN); } } @@ -414,10 +470,9 @@ updateTimer = null; } - setState(PROXY_HIDDEN); + setState(PROXY_DISABLED); - HMD.displayModeChanged.disconnect(onMountedChanged); - HMD.mountedChanged.disconnect(onMountedChanged); + HMD.displayModeChanged.disconnect(onDisplayModeChanged); Messages.messageReceived.disconnect(onMessageReceived); Messages.unsubscribe(HIFI_OBJECT_MANIPULATION_CHANNEL); From 4e23caf46deebd3a7f28fcd3179f735a47ec6cbb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 20 Aug 2018 17:57:59 +1200 Subject: [PATCH 051/744] Fix tablet proper not being grabbed once mini tablet has expanded --- scripts/system/miniTablet.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index f0741582c3..08e7103ba4 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -89,7 +89,7 @@ LEFT_HAND = 0, RIGHT_HAND = 1, HAND_NAMES = ["LeftHand", "RightHand"], - DEBUG = true; + DEBUG = false; // #region Utilities ======================================================================================================= @@ -208,6 +208,7 @@ function hideUI() { Overlays.editOverlay(proxyOverlay, { + parentID: Uuid.NULL, // Release hold so that hand can grab tablet proper. visible: false }); Overlays.editOverlay(proxyUIOverlay, { From 2261ad2bcbde983c607a4249477db4c7104d9dc6 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 20 Aug 2018 11:14:48 -0700 Subject: [PATCH 052/744] clean up the interstitial page --- interface/src/Application.cpp | 5 +-- scripts/system/interstitialPage.js | 58 ++++-------------------------- 2 files changed, 9 insertions(+), 54 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 12a3ebf887..89d1b24736 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3459,10 +3459,11 @@ bool Application::isInterstitialMode() const { } void Application::setIsInterstitialMode(bool interstitialMode) { - bool interstitialModeEnabled = Menu::getInstance()->isOptionChecked("Enable Interstitial"); + auto menu = Menu::getInstance(); + bool interstitialModeEnabled = menu->isOptionChecked("Enable Interstitial"); if (_interstitialMode != interstitialMode && interstitialModeEnabled) { _interstitialMode = interstitialMode; - qDebug() << "-------> interstitial mode changed: " << _interstitialMode << " ------------> "; + menu->setIsOptionChecked(MenuOption::Overlays, !_interstitialMode); emit interstitialModeChanged(_interstitialMode); } } diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 87f415130f..dde3448b11 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -10,11 +10,11 @@ // /* global Script, Controller, Overlays, Quat, MyAvatar, Entities, print, Vec3, AddressManager, Render, Window, Toolbars, - Camera, HMD, location, Account*/ + Camera, HMD, location, Account, Xform*/ (function() { Script.include("/~/system/libraries/Xform.js"); - var DEBUG = true; + var DEBUG = false; var MAX_X_SIZE = 3.8; var EPSILON = 0.01; var isVisible = false; @@ -24,7 +24,6 @@ var sample = null; var MAX_LEFT_MARGIN = 1.9; var INNER_CIRCLE_WIDTH = 4.7; - var DESTINATION_CARD_Y_OFFSET = 2; var DEFAULT_Z_OFFSET = 5.45; var renderViewTask = Render.getConfig("RenderMainView"); @@ -83,7 +82,7 @@ }); - var domainName = "Test"; + var domainName = ""; var domainNameTextID = Overlays.addOverlay("text3d", { name: "Loading-Destination-Card-Text", localPosition: { x: 0.0, y: 0.8, z: 0.0 }, @@ -179,9 +178,7 @@ var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; - var timerset = false; var lastInterval = Date.now(); - var timeElapsed = 0; var currentDomain = ""; var timer = null; var target = 0; @@ -206,16 +203,12 @@ return leftMargin; } - function resetValues() { - } - function lerp(a, b, t) { return ((1 - t) * a + t * b); } function startInterstitialPage() { if (timer === null) { - print("----------> starting <----------"); updateOverlays(Window.isPhysicsEnabled()); startAudio(); target = 0; @@ -255,7 +248,6 @@ uri: url }, function(error, data) { if (data.status === "success") { - print("-----------> settings domain description <----------"); var domainInfo = data.data; var domainDescriptionText = domainInfo.place.description; print("domainText: " + domainDescriptionText); @@ -346,44 +338,11 @@ Overlays.editOverlay(anchorOverlay, { localPosition: localPosition }); } - var progress = 0; - function updateProgress() { - print("updateProgress"); - var thisInterval = Date.now(); - var deltaTime = (thisInterval - lastInterval); - lastInterval = thisInterval; - timeElapsed += deltaTime; - - progress += (deltaTime / 1000); - if (progress > MAX_X_SIZE) { - progress = MAX_X_SIZE; - } - - var properties = { - localPosition: { x: (1.85 - (progress / 2) - (-0.029 * (progress / MAX_X_SIZE))), y: -0.935, z: 0.0 }, - dimensions: { - x: progress, - y: 2.8 - } - }; - - if (progress >= MAX_X_SIZE) { - progress = 0; - } - - Overlays.editOverlay(loadingBarProgress, properties); - - if (!toggle) { - Script.setTimeout(updateProgress, BASIC_TIMER_INTERVAL_MS); - } - } - function update() { var physicsEnabled = Window.isPhysicsEnabled(); var thisInterval = Date.now(); var deltaTime = (thisInterval - lastInterval); lastInterval = thisInterval; - timeElapsed += deltaTime; var nearbyEntitiesReadyCount = Window.getPhysicsNearbyEntitiesReadyCount(); var stabilityCount = Window.getPhysicsNearbyEntitiesStabilityCount(); @@ -407,16 +366,15 @@ } currentProgress = lerp(currentProgress, target, 0.2); var properties = { - localPosition: { x: (1.85 - (progress / 2) - (-0.029 * (progress / MAX_X_SIZE))), y: -0.935, z: 0.0 }, + localPosition: { x: (1.85 - (currentProgress / 2) - (-0.029 * (currentProgress / MAX_X_SIZE))), y: -0.935, z: 0.0 }, dimensions: { x: currentProgress, y: 2.8 } }; - print("progress: " + currentProgress); + Overlays.editOverlay(loadingBarProgress, properties); if ((physicsEnabled && (currentProgress >= (MAX_X_SIZE - EPSILON)))) { - print("----------> ending <--------"); updateOverlays((physicsEnabled || connectionToDomainFailed)); endAudio(); timer = null; @@ -431,7 +389,7 @@ Script.setTimeout(function() { print("location connected: " + location.isConnected); connectionToDomainFailed = !location.isConnected; - }, 300); + }, 1200); }); MyAvatar.sensorToWorldScaleChanged.connect(scaleInterstitialPage); @@ -444,10 +402,6 @@ button.clicked.connect(function() { toggle = !toggle; updateOverlays(toggle); - - if (!toggle) { - // Script.setTimeout(updateProgress, BASIC_TIMER_INTERVAL_MS); - } }); } From 6791d3f1b6c6939bb9858c123a29e344aaa785dc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 21 Aug 2018 08:36:04 +1200 Subject: [PATCH 053/744] Mini tablet button icons and states --- scripts/system/html/css/miniTablet.css | 4 +- scripts/system/html/js/miniTablet.js | 88 ++++++++++++++++++++++++++ scripts/system/html/miniTablet.html | 1 + scripts/system/miniTablet.js | 67 +++++++++++++++++++- 4 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 scripts/system/html/js/miniTablet.js diff --git a/scripts/system/html/css/miniTablet.css b/scripts/system/html/css/miniTablet.css index d0585e489a..5ff9944a2c 100644 --- a/scripts/system/html/css/miniTablet.css +++ b/scripts/system/html/css/miniTablet.css @@ -96,7 +96,7 @@ footer { } footer .button:hover { - border: 1px solid #1fc6a6; + border: 1px solid #e3e3e3; border-radius: 2px; margin: -1px; - } \ No newline at end of file + } diff --git a/scripts/system/html/js/miniTablet.js b/scripts/system/html/js/miniTablet.js new file mode 100644 index 0000000000..450a87ff8d --- /dev/null +++ b/scripts/system/html/js/miniTablet.js @@ -0,0 +1,88 @@ +// +// miniTablet.js +// +// Created by David Rowe on 20 Aug 2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global EventBridge */ +/* eslint-env browser */ + +(function () { + + "use strict"; + + var // EventBridge + READY_MESSAGE = "ready", // Engine <== Dialog + MUTE_MESSAGE = "mute", // Engine <=> Dialog + BUBBLE_MESSAGE = "bubble", // Engine <=> Dialog + + muteButton, + muteImage, + bubbleButton, + bubbleImage; + + // #region Communications ================================================================================================== + + function onScriptEventReceived(data) { + var message; + + try { + message = JSON.parse(data); + } catch (e) { + console.error("EventBridge message error"); + return; + } + + switch (message.type) { + case MUTE_MESSAGE: + muteImage.src = message.icon; + break; + case BUBBLE_MESSAGE: + bubbleButton.classList.remove(message.on ? "off" : "on"); + bubbleButton.classList.add(message.on ? "on" : "off"); + bubbleImage.src = message.icon; + break; + } + } + + function connectEventBridge() { + EventBridge.scriptEventReceived.connect(onScriptEventReceived); + EventBridge.emitWebEvent(JSON.stringify({ + type: READY_MESSAGE + })); + } + + function disconnectEventBridge() { + EventBridge.scriptEventReceived.disconnect(onScriptEventReceived); + } + + // #endregion + + // #region Set-up and tear-down ============================================================================================ + + function onUnload() { + disconnectEventBridge(); + } + + function onLoad() { + muteButton = document.getElementById("mute"); + muteImage = document.getElementById("mute-img"); + bubbleButton = document.getElementById("bubble"); + bubbleImage = document.getElementById("bubble-img"); + + connectEventBridge(); + + document.body.onunload = function () { + onUnload(); + }; + } + + onLoad(); + + // #endregion + +}()); diff --git a/scripts/system/html/miniTablet.html b/scripts/system/html/miniTablet.html index f8216e5515..3ba8d66196 100644 --- a/scripts/system/html/miniTablet.html +++ b/scripts/system/html/miniTablet.html @@ -27,5 +27,6 @@ See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.

...

+ diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 08e7103ba4..044500979c 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -51,6 +51,12 @@ PROXY_UI_LOCAL_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), proxyUIOverlayEnabled = false, PROXY_UI_OVERLAY_ENABLED_DELAY = 500, + proxyOverlayObject = null, + + MUTE_ON_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-mute-a.svg", + MUTE_OFF_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-unmute-i.svg", + BUBBLE_ON_ICON = Script.resourcesPath() + "icons/tablet-icons/bubble-a.svg", + BUBBLE_OFF_ICON = Script.resourcesPath() + "icons/tablet-icons/bubble-i.svg", // State machine PROXY_DISABLED = 0, @@ -77,6 +83,11 @@ proxyTargetWidth, tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"), + // EventBridge + READY_MESSAGE = "ready", // Engine <== Dialog + MUTE_MESSAGE = "mute", // Engine <=> Dialog + BUBBLE_MESSAGE = "bubble", // Engine <=> Dialog + // Events MIN_HAND_CAMERA_ANGLE = 30, DEGREES_180 = 180, @@ -136,6 +147,47 @@ // #endregion + // #region Communications ================================================================================================== + + function updateMutedStatus() { + var isMuted = Audio.muted; + proxyOverlayObject.emitScriptEvent(JSON.stringify({ + type: MUTE_MESSAGE, + on: isMuted, + icon: isMuted ? MUTE_ON_ICON : MUTE_OFF_ICON + })); + } + + function updateBubbleStatus() { + var isBubbleOn = Users.getIgnoreRadiusEnabled(); + proxyOverlayObject.emitScriptEvent(JSON.stringify({ + type: BUBBLE_MESSAGE, + on: isBubbleOn, + icon: isBubbleOn ? BUBBLE_ON_ICON : BUBBLE_OFF_ICON + })); + } + + function onWebEventReceived(data) { + var message; + + try { + message = JSON.parse(data); + } catch (e) { + console.error("EventBridge message error"); + return; + } + + switch (message.type) { + case READY_MESSAGE: + // Send initial button statuses. + updateMutedStatus(); + updateBubbleStatus(); + break; + } + } + + // #endregion + // #region UI ============================================================================================================== function createUI() { @@ -161,6 +213,9 @@ }); proxyUIOverlayEnabled = false; // This and alpha = 0 hides overlay while its content is being created. + + proxyOverlayObject = Overlays.getOverlayObject(proxyUIOverlay); + proxyOverlayObject.webEventReceived.connect(onWebEventReceived); } function showUI(hand) { @@ -217,9 +272,11 @@ } function destroyUI() { - if (proxyOverlay) { + if (proxyOverlayObject) { + proxyOverlayObject.webEventReceived.disconnect(onWebEventReceived); Overlays.deleteOverlay(proxyUIOverlay); Overlays.deleteOverlay(proxyOverlay); + proxyOverlayObject = null; proxyUIOverlay = null; proxyOverlay = null; } @@ -236,6 +293,10 @@ updateTimer = null; } + // Stop monitoring mute and bubble changes. + Audio.mutedChanged.disconnect(updateMutedStatus); + Users.ignoreRadiusEnabledChanged.disconnect(updateBubbleStatus); + // Don't keep overlays prepared if in desktop mode. destroyUI(); } @@ -244,6 +305,10 @@ // Create UI so that it's ready to be displayed without seeing artefacts from creating the UI. createUI(); + // Start monitoring mute and bubble changes. + Audio.mutedChanged.connect(updateMutedStatus); + Users.ignoreRadiusEnabledChanged.connect(updateBubbleStatus); + // Start updates. updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL); } From 4268297769387e2e7b37a0e71416ce9368b424cb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 21 Aug 2018 09:17:53 +1200 Subject: [PATCH 054/744] Mini tablet button actions --- scripts/system/html/js/miniTablet.js | 27 ++++++++++++++- scripts/system/miniTablet.js | 51 +++++++++++++++++++--------- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/scripts/system/html/js/miniTablet.js b/scripts/system/html/js/miniTablet.js index 450a87ff8d..1260813448 100644 --- a/scripts/system/html/js/miniTablet.js +++ b/scripts/system/html/js/miniTablet.js @@ -19,11 +19,13 @@ READY_MESSAGE = "ready", // Engine <== Dialog MUTE_MESSAGE = "mute", // Engine <=> Dialog BUBBLE_MESSAGE = "bubble", // Engine <=> Dialog + EXPAND_MESSAGE = "expand", // Engine <== Dialog muteButton, muteImage, bubbleButton, - bubbleImage; + bubbleImage, + expandButton; // #region Communications ================================================================================================== @@ -49,6 +51,24 @@ } } + function onMuteButtonClick() { + EventBridge.emitWebEvent(JSON.stringify({ + type: MUTE_MESSAGE + })); + } + + function onBubbleButtonClick() { + EventBridge.emitWebEvent(JSON.stringify({ + type: BUBBLE_MESSAGE + })); + } + + function onExpandButtonClick() { + EventBridge.emitWebEvent(JSON.stringify({ + type: EXPAND_MESSAGE + })); + } + function connectEventBridge() { EventBridge.scriptEventReceived.connect(onScriptEventReceived); EventBridge.emitWebEvent(JSON.stringify({ @@ -73,9 +93,14 @@ muteImage = document.getElementById("mute-img"); bubbleButton = document.getElementById("bubble"); bubbleImage = document.getElementById("bubble-img"); + expandButton = document.getElementById("expand"); connectEventBridge(); + muteButton.addEventListener("click", onMuteButtonClick, true); + bubbleButton.addEventListener("click", onBubbleButtonClick, true); + expandButton.addEventListener("click", onExpandButtonClick, true); + document.body.onunload = function () { onUnload(); }; diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 044500979c..6f56f4ab93 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -68,13 +68,14 @@ STATE_MACHINE, rezzerState = PROXY_DISABLED, proxyHand, - PROXY_GRAB_HANDLES = [ + PROXY_EXPAND_HANDLES = [ { x: 0.5, y: -0.4, z: 0 }, - { x: -0.5, y: -0.4, z: 0 } + { x: -0.5, y: -0.4, z: 0 }, + { x: 0, y: -0.4, z: 0 } ], - proxyGrabHand, - proxyGrabHandleLocalPosition, - proxyGrabLocalRotation = Quat.IDENTITY, + proxyExpandHand, + proxyExpandLocalPosition, + proxyExpandLocalRotation = Quat.IDENTITY, PROXY_EXPAND_DURATION = 250, PROXY_EXPAND_TIMEOUT = 25, proxyExpandTimer = null, @@ -87,6 +88,7 @@ READY_MESSAGE = "ready", // Engine <== Dialog MUTE_MESSAGE = "mute", // Engine <=> Dialog BUBBLE_MESSAGE = "bubble", // Engine <=> Dialog + EXPAND_MESSAGE = "expand", // Engine <== Dialog // Events MIN_HAND_CAMERA_ANGLE = 30, @@ -97,8 +99,10 @@ HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation", avatarScale = 1, + LEFT_HAND = 0, RIGHT_HAND = 1, + NO_HAND = 2, HAND_NAMES = ["LeftHand", "RightHand"], DEBUG = false; @@ -183,6 +187,18 @@ updateMutedStatus(); updateBubbleStatus(); break; + case MUTE_MESSAGE: + // Toggle mute. + Audio.muted = !Audio.muted; + break; + case BUBBLE_MESSAGE: + // Toggle bubble. + Users.toggleIgnoreRadius(); + break; + case EXPAND_MESSAGE: + // Expand tablet; + setState(PROXY_EXPANDING, NO_HAND); + break; } } @@ -196,6 +212,7 @@ dimensions: Vec3.multiply(avatarScale, PROXY_DIMENSIONS), solid: true, grabbable: true, + showKeyboardFocusHighlight: false, displayInFront: true, visible: false }); @@ -208,6 +225,7 @@ dpi: PROXY_UI_DPI / avatarScale, alpha: 0, // Hide overlay while its content is being created. grabbable: false, + showKeyboardFocusHighlight: false, displayInFront: true, visible: false }); @@ -247,10 +265,10 @@ function sizeUI(scaleFactor) { Overlays.editOverlay(proxyOverlay, { localPosition: - Vec3.sum(proxyGrabHandleLocalPosition, - Vec3.multiplyQbyV(proxyGrabLocalRotation, + Vec3.sum(proxyExpandLocalPosition, + Vec3.multiplyQbyV(proxyExpandLocalRotation, Vec3.multiply(-scaleFactor, - Vec3.multiplyVbyV(PROXY_GRAB_HANDLES[proxyGrabHand], PROXY_DIMENSIONS))) + Vec3.multiplyVbyV(PROXY_EXPAND_HANDLES[proxyExpandHand], PROXY_DIMENSIONS))) ), dimensions: Vec3.multiply(scaleFactor, PROXY_DIMENSIONS) }); @@ -383,13 +401,14 @@ setState(TABLET_OPEN); } - function enterProxyExpanding() { + function enterProxyExpanding(hand) { // Grab details. var properties = Overlays.getProperties(proxyOverlay, ["localPosition", "localRotation"]); - proxyGrabLocalRotation = properties.localRotation; - proxyGrabHandleLocalPosition = Vec3.sum(properties.localPosition, - Vec3.multiplyQbyV(proxyGrabLocalRotation, - Vec3.multiplyVbyV(PROXY_GRAB_HANDLES[proxyGrabHand], PROXY_DIMENSIONS))); + proxyExpandHand = hand; + proxyExpandLocalRotation = properties.localRotation; + proxyExpandLocalPosition = Vec3.sum(properties.localPosition, + Vec3.multiplyQbyV(proxyExpandLocalRotation, + Vec3.multiplyVbyV(PROXY_EXPAND_HANDLES[proxyExpandHand], PROXY_DIMENSIONS))); // Start expanding. proxyInitialWidth = PROXY_DIMENSIONS.x; @@ -488,7 +507,7 @@ } function onMessageReceived(channel, data, senderID, localOnly) { - var message; + var message, hand; if (channel !== HIFI_OBJECT_MANIPULATION_CHANNEL) { return; @@ -500,8 +519,8 @@ } if (message.action === "grab" && rezzerState === PROXY_VISIBLE) { - proxyGrabHand = message.joint === HAND_NAMES[proxyHand] ? proxyHand : otherHand(proxyHand); - setState(PROXY_EXPANDING); + hand = message.joint === HAND_NAMES[proxyHand] ? proxyHand : otherHand(proxyHand); + setState(PROXY_EXPANDING, hand); } } From f4a8dc49e963df2354526b0f109ba94e16fbd2f4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 21 Aug 2018 10:28:23 +1200 Subject: [PATCH 055/744] Add button sounds --- scripts/system/assets/sounds/button-click.wav | Bin 0 -> 26898 bytes scripts/system/assets/sounds/button-hover.wav | Bin 0 -> 22432 bytes scripts/system/html/js/miniTablet.js | 10 +++++++ scripts/system/miniTablet.js | 25 ++++++++++++++++++ 4 files changed, 35 insertions(+) create mode 100644 scripts/system/assets/sounds/button-click.wav create mode 100644 scripts/system/assets/sounds/button-hover.wav diff --git a/scripts/system/assets/sounds/button-click.wav b/scripts/system/assets/sounds/button-click.wav new file mode 100644 index 0000000000000000000000000000000000000000..30a097ce456369195b2791ebe83fe4d20d0a0c57 GIT binary patch literal 26898 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 Dialog BUBBLE_MESSAGE = "bubble", // Engine <=> Dialog EXPAND_MESSAGE = "expand", // Engine <== Dialog @@ -57,6 +58,12 @@ })); } + function onButtonHover() { + EventBridge.emitWebEvent(JSON.stringify({ + type: HOVER_MESSAGE + })); + } + function onBubbleButtonClick() { EventBridge.emitWebEvent(JSON.stringify({ type: BUBBLE_MESSAGE @@ -97,6 +104,9 @@ connectEventBridge(); + muteButton.addEventListener("mouseenter", onButtonHover, false); + bubbleButton.addEventListener("mouseenter", onButtonHover, false); + expandButton.addEventListener("mouseenter", onButtonHover, false); muteButton.addEventListener("click", onMuteButtonClick, true); bubbleButton.addEventListener("click", onBubbleButtonClick, true); expandButton.addEventListener("click", onExpandButtonClick, true); diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 6f56f4ab93..a16ae50b29 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -86,6 +86,7 @@ // EventBridge READY_MESSAGE = "ready", // Engine <== Dialog + HOVER_MESSAGE = "hover", // Engine <== Dialog MUTE_MESSAGE = "mute", // Engine <=> Dialog BUBBLE_MESSAGE = "bubble", // Engine <=> Dialog EXPAND_MESSAGE = "expand", // Engine <== Dialog @@ -99,11 +100,20 @@ HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation", avatarScale = 1, + // Sounds + HOVER_SOUND = "./assets/sounds/button-hover.wav", + HOVER_VOLUME = 0.5, + CLICK_SOUND = "./assets/sounds/button-click.wav", + CLICK_VOLUME = 0.8, + hoverSound = SoundCache.getSound(Script.resolvePath(HOVER_SOUND)), + clickSound = SoundCache.getSound(Script.resolvePath(CLICK_SOUND)), + // Hands LEFT_HAND = 0, RIGHT_HAND = 1, NO_HAND = 2, HAND_NAMES = ["LeftHand", "RightHand"], + DEBUG = false; // #region Utilities ======================================================================================================= @@ -149,6 +159,14 @@ return hand === LEFT_HAND ? RIGHT_HAND : LEFT_HAND; } + function playSound(sound, volume) { + Audio.playSound(sound, { + position: proxyHand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(), + volume: volume, + localOnly: true + }); + } + // #endregion // #region Communications ================================================================================================== @@ -187,16 +205,23 @@ updateMutedStatus(); updateBubbleStatus(); break; + case HOVER_MESSAGE: + // Audio feedback. + playSound(hoverSound, HOVER_VOLUME); + break; case MUTE_MESSAGE: // Toggle mute. + playSound(clickSound, CLICK_VOLUME); Audio.muted = !Audio.muted; break; case BUBBLE_MESSAGE: // Toggle bubble. + playSound(clickSound, CLICK_VOLUME); Users.toggleIgnoreRadius(); break; case EXPAND_MESSAGE: // Expand tablet; + playSound(clickSound, CLICK_VOLUME); setState(PROXY_EXPANDING, NO_HAND); break; } From dc86230e40bda530ec7ceda38c9a432f5b661448 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 21 Aug 2018 10:33:13 +1200 Subject: [PATCH 056/744] Make "..." expand tablet per display hand rather than centered --- scripts/system/miniTablet.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index a16ae50b29..f7a262b9c4 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -70,8 +70,7 @@ proxyHand, PROXY_EXPAND_HANDLES = [ { x: 0.5, y: -0.4, z: 0 }, - { x: -0.5, y: -0.4, z: 0 }, - { x: 0, y: -0.4, z: 0 } + { x: -0.5, y: -0.4, z: 0 } ], proxyExpandHand, proxyExpandLocalPosition, @@ -111,7 +110,6 @@ // Hands LEFT_HAND = 0, RIGHT_HAND = 1, - NO_HAND = 2, HAND_NAMES = ["LeftHand", "RightHand"], DEBUG = false; @@ -222,7 +220,7 @@ case EXPAND_MESSAGE: // Expand tablet; playSound(clickSound, CLICK_VOLUME); - setState(PROXY_EXPANDING, NO_HAND); + setState(PROXY_EXPANDING, proxyHand); break; } } From 69e5eb9b1c8f3a90cc9058d2a3d8d277ecbe8539 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 21 Aug 2018 10:46:48 +1200 Subject: [PATCH 057/744] Move tablet towards fingertips as it expands --- scripts/system/miniTablet.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index f7a262b9c4..6d6180a964 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -68,9 +68,9 @@ STATE_MACHINE, rezzerState = PROXY_DISABLED, proxyHand, - PROXY_EXPAND_HANDLES = [ - { x: 0.5, y: -0.4, z: 0 }, - { x: -0.5, y: -0.4, z: 0 } + PROXY_EXPAND_HANDLES = [ // Normalized coordinates in range [-0.5, 0.5] about center of mini tablet. + { x: 0.5, y: -0.75, z: 0 }, + { x: -0.5, y: -0.75, z: 0 } ], proxyExpandHand, proxyExpandLocalPosition, From 8a8bf6dc046f179371c94c1f6f8e8e90ab0b3d67 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 21 Aug 2018 10:49:19 +1200 Subject: [PATCH 058/744] Reduce default size of tablet in HMD mode --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 45ef336333..14a9af09d2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -971,7 +971,7 @@ Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); Setting::Handle sessionRunTime{ "sessionRunTime", 0 }; -const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 70.0f; +const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 60.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; From 2dcdd607508eaa9dd98799915102913701a54cb3 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 20 Aug 2018 17:32:52 -0700 Subject: [PATCH 059/744] added new AvatarConstants and more frequent horizontal recentering to deal with lurching walk in large tracked volumes --- interface/src/avatar/MyAvatar.cpp | 19 +++++++++++-------- libraries/shared/src/AvatarConstants.h | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d69ba21f6d..d1147cc796 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3557,10 +3557,10 @@ static bool headAngularVelocityBelowThreshold(const controller::Pose& head) { return isBelowThreshold; } -static bool isWithinThresholdHeightMode(const controller::Pose& head,const float& newMode) { +static bool isWithinThresholdHeightMode(const controller::Pose& head, const float& newMode, const float& scale) { bool isWithinThreshold = true; if (head.isValid()) { - isWithinThreshold = (head.getTranslation().y - newMode) > DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD; + isWithinThreshold = (head.getTranslation().y - newMode) > DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD * scale; } return isWithinThreshold; } @@ -3791,8 +3791,7 @@ void MyAvatar::lateUpdatePalms() { Avatar::updatePalms(); } - -static const float FOLLOW_TIME = 0.5f; +static const float FOLLOW_TIME = 0.1f; MyAvatar::FollowHelper::FollowHelper() { deactivate(); @@ -3883,9 +3882,10 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); bool stepDetected = false; + float myScale = myAvatar.getAvatarScale(); if (!withinBaseOfSupport(currentHeadPose) && headAngularVelocityBelowThreshold(currentHeadPose) && - isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight()) && + isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight(), myScale) && handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && headVelocityGreaterThanThreshold(currentHeadPose) && @@ -3897,8 +3897,8 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); - if (!isActive(Horizontal) && - (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + DEFAULT_AVATAR_SPINE_STRETCH_LIMIT))) { + if (!isActive(Horizontal) && + (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance * myAvatar.getAvatarScale())))) { myAvatar.setResetMode(true); stepDetected = true; } @@ -3918,6 +3918,9 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { + const float VELOCITY_THRESHHOLD = 0.5f; + float currentVelocity = glm::length(myAvatar.getLocalVelocity() / myAvatar.getSensorToWorldScale()); + if (myAvatar.getHMDLeanRecenterEnabled() && qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { @@ -3925,7 +3928,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat myAvatar.setHeadControllerFacingMovingAverage(myAvatar._headControllerFacing); } if (myAvatar.getCenterOfGravityModelEnabled()) { - if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { + if ((!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) || ( isActive(Horizontal) && (currentVelocity > VELOCITY_THRESHHOLD))) { activate(Horizontal); } } else { diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index cdc571d1aa..c3e8a3f173 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -34,7 +34,7 @@ const float DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 3.3f; const float DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD = 0.18f; const float DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE = 7.0f; const float DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE = 7.0f; -const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.03f; +const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.04f; const float DEFAULT_AVATAR_FORWARD_DAMPENING_FACTOR = 0.5f; const float DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR = 2.0f; const float DEFAULT_AVATAR_HIPS_MASS = 40.0f; From 1e2ca579c776287652c60399ab0703539cc4a85e Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 20 Aug 2018 18:06:34 -0700 Subject: [PATCH 060/744] widened base of support and put head angular and hand angular back to normal --- libraries/shared/src/AvatarConstants.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index c3e8a3f173..4e6fa67c9f 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -24,10 +24,10 @@ const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f; const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.12f; -const float DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD = 0.10f; -const float DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD = 0.04f; -const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.05f; -const float DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 0.3f; +const float DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD = 0.15f; +const float DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD = 0.07f; +const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.08f; +const float DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 0.6f; const float DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD = -0.02f; const float DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD = 0.4f; const float DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 3.3f; From 5e7f68d4b7c7d080a4ee7316b0e09f111b84c8d3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 21 Aug 2018 14:00:20 +1200 Subject: [PATCH 061/744] Use a smaller near grab radius for tablet and mini-tablet --- .../controllers/controllerDispatcher.js | 23 +++++++++++++++++++ scripts/system/miniTablet.js | 8 +++++++ 2 files changed, 31 insertions(+) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 7a916392b9..f71af03e26 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -25,7 +25,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); (function() { Script.include("/~/system/libraries/pointersUtils.js"); + var NEAR_MAX_RADIUS = 0.1; + var NEAR_TABLET_MAX_RADIUS = 0.05; var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; @@ -49,6 +51,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.tabletID = null; this.blacklist = []; this.pointerManager = new PointerManager(); + this.miniTabletID = null; // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name @@ -211,6 +214,22 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); if (controllerLocations[h].valid) { var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS * sensorScaleFactor); + + // Tablet and mini-tablet must be within NEAR_TABLET_MAX_RADIUS in order to be grabbed. + var tabletIndex = nearbyOverlays.indexOf(HMD.tabletID); + var miniTabletIndex = nearbyOverlays.indexOf(_this.miniTabletID); + if (tabletIndex !== -1 || miniTabletIndex !== -1) { + var closebyOverlays = + Overlays.findOverlays(controllerLocations[h].position, NEAR_TABLET_MAX_RADIUS * sensorScaleFactor); + // Assumes that the tablet and mini-tablet are not displayed at the same time. + if (tabletIndex !== -1 && closebyOverlays.indexOf(HMD.tabletID) === -1) { + nearbyOverlays.splice(tabletIndex, 1); + } + if (miniTabletIndex !== -1 && closebyOverlays.indexOf(_this.miniTabletID) === -1) { + nearbyOverlays.splice(miniTabletIndex, 1); + } + } + nearbyOverlays.sort(function (a, b) { var aPosition = Overlays.getProperty(a, "position"); var aDistance = Vec3.distance(aPosition, controllerLocations[h].position); @@ -218,6 +237,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var bDistance = Vec3.distance(bPosition, controllerLocations[h].position); return aDistance - bDistance; }); + nearbyOverlayIDs.push(nearbyOverlays); } else { nearbyOverlayIDs.push([]); @@ -470,6 +490,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); _this.setBlacklist(); } } + } else if (channel === 'Hifi-MiniTablet-ID') { + _this.miniTabletID = message; } } catch (e) { @@ -508,6 +530,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Entities.mousePressOnEntity.connect(mousePress); var controllerDispatcher = new ControllerDispatcher(); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); + Messages.subscribe('Hifi-MiniTablet-ID'); Messages.messageReceived.connect(controllerDispatcher.handleHandMessage); Script.scriptEnding.connect(controllerDispatcher.cleanup); Script.setTimeout(controllerDispatcher.update, BASIC_TIMER_INTERVAL_MS); diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 6d6180a964..fdfa92d98a 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -169,6 +169,11 @@ // #region Communications ================================================================================================== + function updateMiniTabletID() { + // Send mini-tablet overlay ID to controllerDispatcher so that it can use a smaller near grab distance. + Messages.sendLocalMessage("Hifi-MiniTablet-ID", proxyOverlay); + } + function updateMutedStatus() { var isMuted = Audio.muted; proxyOverlayObject.emitScriptEvent(JSON.stringify({ @@ -257,6 +262,8 @@ proxyOverlayObject = Overlays.getOverlayObject(proxyUIOverlay); proxyOverlayObject.webEventReceived.connect(onWebEventReceived); + + updateMiniTabletID(); } function showUI(hand) { @@ -320,6 +327,7 @@ proxyOverlayObject = null; proxyUIOverlay = null; proxyOverlay = null; + updateMiniTabletID(); } } From f6a607efcc3b4f563cc5d75cbb6336004179ad4c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 21 Aug 2018 15:43:37 +1200 Subject: [PATCH 062/744] Expand / shrink mini-tablet when showing / hiding --- scripts/system/miniTablet.js | 130 +++++++++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 20 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index fdfa92d98a..dcbaa34ad5 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -61,13 +61,20 @@ // State machine PROXY_DISABLED = 0, PROXY_HIDDEN = 1, - PROXY_VISIBLE = 2, - PROXY_EXPANDING = 3, - TABLET_OPEN = 4, - STATE_STRINGS = ["PROXY_DISABLED", "PROXY_HIDDEN", "PROXY_VISIBLE", "PROXY_EXPANDING", "TABLET_OPEN"], + PROXY_HIDING = 2, + PROXY_SHOWING = 3, + PROXY_VISIBLE = 4, + PROXY_EXPANDING = 5, + TABLET_OPEN = 6, + STATE_STRINGS = ["PROXY_DISABLED", "PROXY_HIDDEN", "PROXY_HIDING", "PROXY_SHOWING", "PROXY_VISIBLE", "PROXY_EXPANDING", + "TABLET_OPEN"], STATE_MACHINE, rezzerState = PROXY_DISABLED, proxyHand, + PROXY_SCALE_DURATION = 150, + PROXY_SCALE_TIMEOUT = 20, + proxyScaleTimer = null, + proxyScaleStart, PROXY_EXPAND_HANDLES = [ // Normalized coordinates in range [-0.5, 0.5] about center of mini tablet. { x: 0.5, y: -0.75, z: 0 }, { x: -0.5, y: -0.75, z: 0 } @@ -76,12 +83,11 @@ proxyExpandLocalPosition, proxyExpandLocalRotation = Quat.IDENTITY, PROXY_EXPAND_DURATION = 250, - PROXY_EXPAND_TIMEOUT = 25, + PROXY_EXPAND_TIMEOUT = 20, proxyExpandTimer = null, proxyExpandStart, proxyInitialWidth, proxyTargetWidth, - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"), // EventBridge READY_MESSAGE = "ready", // Engine <== Dialog @@ -112,6 +118,8 @@ RIGHT_HAND = 1, HAND_NAMES = ["LeftHand", "RightHand"], + // Miscellaneous. + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"), DEBUG = false; // #region Utilities ======================================================================================================= @@ -266,20 +274,22 @@ updateMiniTabletID(); } - function showUI(hand) { + function showUI() { + var initialScale = 0.01; // Start very small. + Overlays.editOverlay(proxyOverlay, { parentID: MyAvatar.SELF_ID, parentJointIndex: handJointIndex(proxyHand), localPosition: Vec3.multiply(avatarScale, proxyHand === LEFT_HAND ? PROXY_POSITION_LEFT_HAND : PROXY_POSITION_RIGHT_HAND), localRotation: proxyHand === LEFT_HAND ? PROXY_ROTATION_LEFT_HAND : PROXY_ROTATION_RIGHT_HAND, - dimensions: Vec3.multiply(avatarScale, PROXY_DIMENSIONS), + dimensions: Vec3.multiply(initialScale, PROXY_DIMENSIONS), visible: true }); Overlays.editOverlay(proxyUIOverlay, { localPosition: Vec3.multiply(avatarScale, PROXY_UI_LOCAL_POSITION), - dimensions: Vec3.multiply(avatarScale, PROXY_UI_DIMENSIONS), - dpi: PROXY_UI_DPI / avatarScale, + dimensions: Vec3.multiply(initialScale, PROXY_UI_DIMENSIONS), + dpi: PROXY_UI_DPI / initialScale, visible: true }); @@ -293,6 +303,19 @@ } function sizeUI(scaleFactor) { + // Scale UI in place. + Overlays.editOverlay(proxyOverlay, { + dimensions: Vec3.multiply(scaleFactor, PROXY_DIMENSIONS) + }); + Overlays.editOverlay(proxyUIOverlay, { + localPosition: Vec3.multiply(scaleFactor, PROXY_UI_LOCAL_POSITION), + dimensions: Vec3.multiply(scaleFactor, PROXY_UI_DIMENSIONS), + dpi: PROXY_UI_DPI / scaleFactor + }); + } + + function sizeUIAboutHandles(scaleFactor) { + // Scale UI and move per handles. Overlays.editOverlay(proxyOverlay, { localPosition: Vec3.sum(proxyExpandLocalPosition, @@ -396,15 +419,71 @@ } // Compare palm directions of hands with vectors from palms to camera. if (shouldShowProxy(LEFT_HAND)) { - setState(PROXY_VISIBLE, LEFT_HAND); + setState(PROXY_SHOWING, LEFT_HAND); } else if (shouldShowProxy(RIGHT_HAND)) { - setState(PROXY_VISIBLE, RIGHT_HAND); + setState(PROXY_SHOWING, RIGHT_HAND); } } - function enterProxyVisible(hand) { + function scaleProxyDown() { + var scaleFactor = (Date.now() - proxyScaleStart) / PROXY_SCALE_DURATION; + if (scaleFactor < 1) { + sizeUI((1 - scaleFactor) * avatarScale); + proxyScaleTimer = Script.setTimeout(scaleProxyDown, PROXY_SCALE_TIMEOUT); + return; + } + proxyScaleTimer = null; + setState(PROXY_HIDDEN); + } + + function enterProxyHiding() { + proxyScaleStart = Date.now(); + proxyScaleTimer = Script.setTimeout(scaleProxyDown, PROXY_SCALE_TIMEOUT); + } + + function updateProxyHiding() { + if (HMD.showTablet) { + setState(PROXY_HIDDEN); + } + } + + function exitProxyHiding() { + if (proxyScaleTimer) { + Script.clearTimeout(proxyScaleTimer); + proxyScaleTimer = null; + } + } + + function scaleProxyUp() { + var scaleFactor = (Date.now() - proxyScaleStart) / PROXY_SCALE_DURATION; + if (scaleFactor < 1) { + sizeUI(scaleFactor * avatarScale); + proxyScaleTimer = Script.setTimeout(scaleProxyUp, PROXY_SCALE_TIMEOUT); + return; + } + proxyScaleTimer = null; + sizeUI(avatarScale); + setState(PROXY_VISIBLE); + } + + function enterProxyShowing(hand) { proxyHand = hand; - showUI(proxyHand); + showUI(); + proxyScaleStart = Date.now(); + proxyScaleTimer = Script.setTimeout(scaleProxyUp, PROXY_SCALE_TIMEOUT); + } + + function updateProxyShowing() { + if (HMD.showTablet) { + setState(PROXY_HIDDEN); + } + } + + function exitProxyShowing() { + if (proxyScaleTimer) { + Script.clearTimeout(proxyScaleTimer); + proxyScaleTimer = null; + } } function updateProxyVisible() { @@ -415,7 +494,7 @@ } // Check that palm direction of proxy hand still less than maximum angle. if (!shouldShowProxy(proxyHand)) { - setState(PROXY_HIDDEN); + setState(PROXY_HIDING); } } @@ -423,11 +502,10 @@ var scaleFactor = (Date.now() - proxyExpandStart) / PROXY_EXPAND_DURATION; var tabletScaleFactor = avatarScale * (1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth); if (scaleFactor < 1) { - sizeUI(tabletScaleFactor); + sizeUIAboutHandles(tabletScaleFactor); proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); return; } - proxyExpandTimer = null; setState(TABLET_OPEN); } @@ -449,7 +527,7 @@ } function updateProxyExanding() { - // Hide proxy if tablet has been displayed by other means. + // Hide proxy immediately if tablet has been displayed by other means. if (HMD.showTablet) { setState(PROXY_HIDDEN); } @@ -490,8 +568,18 @@ update: updateProxyHidden, exit: null }, + PROXY_HIDING: { // Tablet proxy is reducing from PROXY_VISIBLE to PROXY_HIDDEN. + enter: enterProxyHiding, + update: updateProxyHiding, + exit: exitProxyHiding + }, + PROXY_SHOWING: { // Tablet proxy is expanding from PROXY_HIDDN to PROXY_VISIBLE. + enter: enterProxyShowing, + update: updateProxyShowing, + exit: exitProxyShowing + }, PROXY_VISIBLE: { // Tablet proxy is visible and attached to hand. - enter: enterProxyVisible, + enter: null, update: updateProxyVisible, exit: null }, @@ -523,7 +611,9 @@ } function updateState() { - STATE_MACHINE[STATE_STRINGS[rezzerState]].update(); + if (STATE_MACHINE[STATE_STRINGS[rezzerState]].update) { + STATE_MACHINE[STATE_STRINGS[rezzerState]].update(); + } updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL); } From e25b2f6e0f3e02fc569a126262edfad3737444a2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 21 Aug 2018 16:47:40 +1200 Subject: [PATCH 063/744] Support stylus input on the mini tablet --- .../controllerModules/stylusInput.js | 27 +++++++++++++++++++ scripts/system/miniTablet.js | 6 ++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/stylusInput.js b/scripts/system/controllers/controllerModules/stylusInput.js index a512fd89db..77615e361f 100644 --- a/scripts/system/controllers/controllerModules/stylusInput.js +++ b/scripts/system/controllers/controllerModules/stylusInput.js @@ -58,6 +58,12 @@ Script.include("/~/system/libraries/controllers.js"); enabled: true }); + this.miniTabletID = null; + + this.setMiniTabletID = function (id) { + this.miniTabletID = id; + }; + this.disable = false; this.otherModuleNeedsToRun = function(controllerData) { @@ -123,6 +129,14 @@ Script.include("/~/system/libraries/controllers.js"); } } + // Add the mini tablet. + if (this.miniTabletID && Overlays.getProperty(this.miniTabletID, "visible")) { + stylusTarget = getOverlayDistance(controllerPosition, this.miniTabletID); + if (stylusTarget) { + stylusTargets.push(stylusTarget); + } + } + var WEB_DISPLAY_STYLUS_DISTANCE = 0.5; var nearStylusTarget = isNearStylusTarget(stylusTargets, WEB_DISPLAY_STYLUS_DISTANCE * sensorScaleFactor); @@ -191,6 +205,15 @@ Script.include("/~/system/libraries/controllers.js"); } } + function onMessageReceived(channel, message, sender) { + if (sender === MyAvatar.sessionUUID) { + if (channel === 'Hifi-MiniTablet-UI-ID') { + leftTabletStylusInput.setMiniTabletID(message); + rightTabletStylusInput.setMiniTabletID(message); + } + } + } + var leftTabletStylusInput = new StylusInput(LEFT_HAND); var rightTabletStylusInput = new StylusInput(RIGHT_HAND); @@ -201,7 +224,11 @@ Script.include("/~/system/libraries/controllers.js"); Overlays.hoverLeaveOverlay.connect(mouseHoverLeave); Overlays.mousePressOnOverlay.connect(mousePress); + Messages.subscribe('Hifi-MiniTablet-UI-ID'); + Messages.messageReceived.connect(onMessageReceived); + this.cleanup = function () { + Messages.messageReceived.disconnect(onMessageReceived); leftTabletStylusInput.cleanup(); rightTabletStylusInput.cleanup(); disableDispatcherModule("LeftTabletStylusInput"); diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index dcbaa34ad5..30594392e2 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -180,6 +180,8 @@ function updateMiniTabletID() { // Send mini-tablet overlay ID to controllerDispatcher so that it can use a smaller near grab distance. Messages.sendLocalMessage("Hifi-MiniTablet-ID", proxyOverlay); + // Send mini-tablet UI overlay ID to stylusInput so that it styluses can be used on it. + Messages.sendLocalMessage("Hifi-MiniTablet-UI-ID", proxyUIOverlay); } function updateMutedStatus() { @@ -271,7 +273,7 @@ proxyOverlayObject = Overlays.getOverlayObject(proxyUIOverlay); proxyOverlayObject.webEventReceived.connect(onWebEventReceived); - updateMiniTabletID(); + // updateMiniTabletID(); Other scripts relying on this may not be ready yet so do this in showUI(). } function showUI() { @@ -293,6 +295,8 @@ visible: true }); + updateMiniTabletID(); + if (!proxyUIOverlayEnabled) { // Overlay content is created the first time it is visible to the user. The initial creation displays artefacts. // Delay showing UI overlay until after giving it time for its content to be created. From 536db64123c76d5854787f85cc50cbdcfae6d644 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 21 Aug 2018 16:48:35 +1200 Subject: [PATCH 064/744] Mitigate against UI overlay possibly getting rotated --- scripts/system/miniTablet.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 30594392e2..5c950cd594 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -290,6 +290,7 @@ }); Overlays.editOverlay(proxyUIOverlay, { localPosition: Vec3.multiply(avatarScale, PROXY_UI_LOCAL_POSITION), + localRotation: PROXY_UI_LOCAL_ROTATION, dimensions: Vec3.multiply(initialScale, PROXY_UI_DIMENSIONS), dpi: PROXY_UI_DPI / initialScale, visible: true From b2fb2c7f9832c529d8c1530f5b350e80db5a31e7 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 21 Aug 2018 10:45:59 -0700 Subject: [PATCH 065/744] fix build error --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index afad67b43e..9d5a7a01f8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1373,7 +1373,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); - connect(this, &Application::interstitialModeChanged, audioIO.data(), &AudioClient::setInterstitialStatus); + connect(this, &Application::interstitialModeChanged, audioIO, &AudioClient::setInterstitialStatus); } // Create the rendering engine. This can be slow on some machines due to lots of From 1fabc77244d2546979e0ed5d95979714b89fc481 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 21 Aug 2018 12:34:05 -0700 Subject: [PATCH 066/744] latest update to stop lurching, and allow leaning, and stop perma leans --- interface/src/avatar/MyAvatar.cpp | 9 ++++++--- libraries/shared/src/AvatarConstants.h | 8 ++++---- scripts/developer/objectOrientedStep.js | 7 ++++++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d1147cc796..ba4636bfa6 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3560,7 +3560,7 @@ static bool headAngularVelocityBelowThreshold(const controller::Pose& head) { static bool isWithinThresholdHeightMode(const controller::Pose& head, const float& newMode, const float& scale) { bool isWithinThreshold = true; if (head.isValid()) { - isWithinThreshold = (head.getTranslation().y - newMode) > DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD * scale; + isWithinThreshold = (head.getTranslation().y - newMode) > (DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD * scale); } return isWithinThreshold; } @@ -3881,6 +3881,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); + bool stepDetected = false; float myScale = myAvatar.getAvatarScale(); if (!withinBaseOfSupport(currentHeadPose) && @@ -3898,10 +3899,12 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); if (!isActive(Horizontal) && - (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance * myAvatar.getAvatarScale())))) { + (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { myAvatar.setResetMode(true); + qCDebug(interfaceapp) << "failsafe called, default back " << anatomicalHeadToHipsDistance << " scale " << myAvatar.getAvatarScale() << " current length " << glm::length(currentHeadPosition - defaultHipsPosition); stepDetected = true; } + qCDebug(interfaceapp) << "current head height " << currentHeadPose.getTranslation().y << " scale " << myAvatar.getAvatarScale() << " anatomical " << anatomicalHeadToHipsDistance; } return stepDetected; } @@ -3918,7 +3921,7 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { - const float VELOCITY_THRESHHOLD = 0.5f; + const float VELOCITY_THRESHHOLD = 1.0f; float currentVelocity = glm::length(myAvatar.getLocalVelocity() / myAvatar.getSensorToWorldScale()); if (myAvatar.getHMDLeanRecenterEnabled() && diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 4e6fa67c9f..6f807a4115 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -24,17 +24,17 @@ const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f; const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.12f; -const float DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD = 0.15f; +const float DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD = 0.10f; const float DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD = 0.07f; -const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.08f; -const float DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 0.6f; +const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.06f; +const float DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 0.4f; const float DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD = -0.02f; const float DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD = 0.4f; const float DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 3.3f; const float DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD = 0.18f; const float DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE = 7.0f; const float DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE = 7.0f; -const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.04f; +const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.05f; const float DEFAULT_AVATAR_FORWARD_DAMPENING_FACTOR = 0.5f; const float DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR = 2.0f; const float DEFAULT_AVATAR_HIPS_MASS = 40.0f; diff --git a/scripts/developer/objectOrientedStep.js b/scripts/developer/objectOrientedStep.js index 4eca1fb115..a5c27e36b9 100644 --- a/scripts/developer/objectOrientedStep.js +++ b/scripts/developer/objectOrientedStep.js @@ -584,7 +584,11 @@ function update(dt) { // make the signal colors reflect the current thresholds that have been crossed updateSignalColors(); - print("the current back length is " + currentStateReadings.backLength + " " + DEFAULT_TORSO_LENGTH); + + SPINE_STRETCH_LIMIT = (0.04) * DEFAULT_TORSO_LENGTH * MyAvatar.scale; + + //print("the spine stretch limit is " + SPINE_STRETCH_LIMIT + " head avatar space is " + currentStateReadings.headPose.translation.y); + //print("the current back length is " + currentStateReadings.backLength + " " + DEFAULT_TORSO_LENGTH); // Conditions for taking a step. // 1. off the base of support. front, lateral, back edges. // 2. head is not lower than the height mode value by more than the maxHeightChange tolerance @@ -672,6 +676,7 @@ Script.setTimeout(function() { DEFAULT_HIPS_POSITION = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(MyAvatar.getJointIndex("Hips")); DEFAULT_HEAD_POSITION = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(MyAvatar.getJointIndex("Head")); DEFAULT_TORSO_LENGTH = Vec3.length(Vec3.subtract(DEFAULT_HEAD_POSITION, DEFAULT_HIPS_POSITION)); + SPINE_STRETCH_LIMIT = (0.04) * DEFAULT_TORSO_LENGTH * MyAvatar.scale; },(4*LOADING_DELAY)); Script.update.connect(update); From 5b4eb922818917a81603b898d019fa8625506f20 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 21 Aug 2018 14:04:29 -0700 Subject: [PATCH 067/744] adding fix --- .../controllers/controllerModules/webSurfaceLaserInput.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index ef9ff0c6ae..4077d07c38 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -76,7 +76,7 @@ Script.include("/~/system/libraries/controllers.js"); var objectID = intersection.objectID; if (intersection.type === Picks.INTERSECTED_OVERLAY) { if ((HMD.tabletID && objectID === HMD.tabletID) || - (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || + (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || (HMD.homeButtonID && objectID === HMD.homeButtonID)) { return true; } else { @@ -122,9 +122,9 @@ Script.include("/~/system/libraries/controllers.js"); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; var allowThisModule = !otherModuleRunning || isTriggerPressed; + if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed) && !this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)) { - //(controllerData.nearbyEntityProperties[this.hand] !== [])) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. @@ -147,7 +147,6 @@ Script.include("/~/system/libraries/controllers.js"); var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) && !this.isPointingAtNearGrabbableEntity(controllerData, isTriggerPressed)) { - //(controllerData.nearbyEntityProperties[this.hand] !== [])) { this.running = true; return makeRunningValues(true, [], []); } From 7360629d31b452e295ead23e38b9f46fbf231a30 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 21 Aug 2018 14:13:55 -0700 Subject: [PATCH 068/744] modifying variable naming to fit convention --- .../controllers/controllerModules/webSurfaceLaserInput.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 4077d07c38..de3eb26086 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -84,9 +84,9 @@ Script.include("/~/system/libraries/controllers.js"); return overlayType === "web3d" || triggerPressed; } } else if (intersection.type === Picks.INTERSECTED_ENTITY) { - var entityProperty = Entities.getEntityProperties(objectID); - var entityType = entityProperty.type; - var isLocked = entityProperty.locked; + var entityProperties = Entities.getEntityProperties(objectID); + var entityType = entityProperties.type; + var isLocked = entityProperties.locked; return entityType === "Web" && (!isLocked || triggerPressed); } return false; From e867ae86f441b6d14a4e6ccc521612e31f34c8d2 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 21 Aug 2018 14:44:55 -0700 Subject: [PATCH 069/744] Split readPendingDatagrams() into two slots --- libraries/networking/src/udt/Socket.cpp | 67 ++++++++++++++++++------- libraries/networking/src/udt/Socket.h | 14 ++++++ 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index c378987cd0..d01b937b8b 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -37,6 +37,7 @@ Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) : _shouldChangeSocketOptions(shouldChangeSocketOptions) { connect(&_udpSocket, &QUdpSocket::readyRead, this, &Socket::readPendingDatagrams); + connect(this, &Socket::pendingDatagrams, this, &Socket::processPendingDatagrams); // make sure we hear about errors and state changes from the underlying socket connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), @@ -315,55 +316,80 @@ void Socket::checkForReadyReadBackup() { } void Socket::readPendingDatagrams() { + int packetsRead = 0; + int packetSizeWithHeader = -1; - while (_udpSocket.hasPendingDatagrams() && (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) { - - // we're reading a packet so re-start the readyRead backup timer - _readyReadBackupTimer->start(); - // grab a time point we can mark as the receive time of this packet auto receiveTime = p_high_resolution_clock::now(); - // setup a HifiSockAddr to read into - HifiSockAddr senderSockAddr; // setup a buffer to read the packet into auto buffer = std::unique_ptr(new char[packetSizeWithHeader]); + QHostAddress senderAddress; + quint16 senderPort; + // pull the datagram auto sizeRead = _udpSocket.readDatagram(buffer.get(), packetSizeWithHeader, - senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); + &senderAddress, &senderPort); - // save information for this packet, in case it is the one that sticks readyRead - _lastPacketSizeRead = sizeRead; - _lastPacketSockAddr = senderSockAddr; + // we either didn't pull anything for this packet or there was an error reading (this seems to trigger + // on windows even if there's not a packet available) - if (sizeRead <= 0) { - // we either didn't pull anything for this packet or there was an error reading (this seems to trigger - // on windows even if there's not a packet available) + if (packetSizeWithHeader < 0) { continue; } + _incomingDatagrams.push({ senderAddress, senderPort, packetSizeWithHeader, + std::move(buffer), receiveTime }); + ++packetsRead; + + } + + _maxDatagramsRead = std::max(_maxDatagramsRead, packetsRead); + emit pendingDatagrams(packetsRead); +} + +void Socket::processPendingDatagrams(int) { + // setup a HifiSockAddr to read into + HifiSockAddr senderSockAddr; + + while (!_incomingDatagrams.empty()) { + auto& datagram = _incomingDatagrams.front(); + senderSockAddr.setAddress(datagram._senderAddress); + senderSockAddr.setPort(datagram._senderPort); + int datagramSize = datagram._datagramLength; + auto receiveTime = datagram._receiveTime; + auto it = _unfilteredHandlers.find(senderSockAddr); if (it != _unfilteredHandlers.end()) { // we have a registered unfiltered handler for this HifiSockAddr - call that and return if (it->second) { - auto basePacket = BasePacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); - basePacket->setReceiveTime(receiveTime); + auto basePacket = BasePacket::fromReceivedPacket(std::move(datagram._datagram), datagramSize, + senderSockAddr); + basePacket->setReceiveTime(datagram._receiveTime); it->second(std::move(basePacket)); } + _incomingDatagrams.pop(); continue; } + // we're reading a packet so re-start the readyRead backup timer + _readyReadBackupTimer->start(); + + // save information for this packet, in case it is the one that sticks readyRead + _lastPacketSizeRead = datagramSize; + _lastPacketSockAddr = senderSockAddr; + // check if this was a control packet or a data packet - bool isControlPacket = *reinterpret_cast(buffer.get()) & CONTROL_BIT_MASK; + bool isControlPacket = *reinterpret_cast(datagram._datagram.get()) & CONTROL_BIT_MASK; if (isControlPacket) { // setup a control packet from the data we just read - auto controlPacket = ControlPacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + auto controlPacket = ControlPacket::fromReceivedPacket(std::move(datagram._datagram), datagramSize, senderSockAddr); controlPacket->setReceiveTime(receiveTime); // move this control packet to the matching connection, if there is one @@ -375,7 +401,7 @@ void Socket::readPendingDatagrams() { } else { // setup a Packet from the data we just read - auto packet = Packet::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + auto packet = Packet::fromReceivedPacket(std::move(datagram._datagram), datagramSize, senderSockAddr); packet->setReceiveTime(receiveTime); // save the sequence number in case this is the packet that sticks readyRead @@ -395,6 +421,7 @@ void Socket::readPendingDatagrams() { qCDebug(networking) << "Can't process packet: version" << (unsigned int)NLPacket::versionInHeader(*packet) << ", type" << NLPacket::typeInHeader(*packet); #endif + _incomingDatagrams.pop(); continue; } } @@ -410,6 +437,8 @@ void Socket::readPendingDatagrams() { } } } + + _incomingDatagrams.pop(); } } diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 1f28592c83..d23e0425a0 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -94,6 +95,7 @@ public: signals: void clientHandshakeRequestComplete(const HifiSockAddr& sockAddr); + void pendingDatagrams(int datagramCount); public slots: void cleanupConnection(HifiSockAddr sockAddr); @@ -101,6 +103,7 @@ public slots: private slots: void readPendingDatagrams(); + void processPendingDatagrams(int datagramCount); void checkForReadyReadBackup(); void handleSocketError(QAbstractSocket::SocketError socketError); @@ -144,6 +147,17 @@ private: int _lastPacketSizeRead { 0 }; SequenceNumber _lastReceivedSequenceNumber; HifiSockAddr _lastPacketSockAddr; + + struct Datagram { + QHostAddress _senderAddress; + int _senderPort; + int _datagramLength; + std::unique_ptr _datagram; + std::chrono::time_point _receiveTime; + }; + + std::queue _incomingDatagrams; + int _maxDatagramsRead { 0 }; friend UDTTest; }; From 4084973cca6d1193e681f53c9acddbc9870a0e38 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 21 Aug 2018 16:04:08 -0700 Subject: [PATCH 070/744] Changes for linux compilation --- libraries/networking/src/udt/Socket.cpp | 2 +- libraries/networking/src/udt/Socket.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index d01b937b8b..090beb2726 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -337,7 +337,7 @@ void Socket::readPendingDatagrams() { // we either didn't pull anything for this packet or there was an error reading (this seems to trigger // on windows even if there's not a packet available) - if (packetSizeWithHeader < 0) { + if (sizeRead < 0) { continue; } diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index d23e0425a0..078863663f 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -153,7 +153,7 @@ private: int _senderPort; int _datagramLength; std::unique_ptr _datagram; - std::chrono::time_point _receiveTime; + p_high_resolution_clock::time_point _receiveTime; }; std::queue _incomingDatagrams; From 5cf6b57f7fa7d374a15f212d43f8541cc080ac8b Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 21 Aug 2018 17:25:21 -0700 Subject: [PATCH 071/744] removing description - hassle-free fix --- scripts/developer/utilities/audio/Stats.qml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/developer/utilities/audio/Stats.qml b/scripts/developer/utilities/audio/Stats.qml index 57963732c8..f359e9b04c 100644 --- a/scripts/developer/utilities/audio/Stats.qml +++ b/scripts/developer/utilities/audio/Stats.qml @@ -40,7 +40,8 @@ Column { Section { label: "Latency" - description: "Audio pipeline latency, broken out and summed" + // description: "Audio pipeline latency, broken out and summed" + description: label control: ColumnLayout { MovingValue { label: "Input Read"; source: AudioStats.inputReadMsMax; showGraphs: stats.showGraphs } MovingValue { label: "Input Ring"; source: AudioStats.inputUnplayedMsMax; showGraphs: stats.showGraphs } @@ -62,7 +63,8 @@ Column { Section { label: "Upstream Jitter" - description: "Timegaps in packets sent to the mixer" + // description: "Timegaps in packets sent to the mixer" + description: label control: Jitter { max: AudioStats.sentTimegapMsMaxWindow avg: AudioStats.sentTimegapMsAvgWindow @@ -76,13 +78,15 @@ Column { Section { label: "Mixer (upstream)" - description: "This client's remote audio stream, as seen by the server's mixer" + // description: "This client's remote audio stream, as seen by the server's mixer" + description: label control: Stream { stream: AudioStats.mixerStream; showGraphs: stats.showGraphs } } Section { label: "Client (downstream)" - description: "This client's received audio stream, between the network and the OS" + // description: "This client's received audio stream, between the network and the OS" + description: label control: Stream { stream: AudioStats.clientStream; showGraphs: stats.showGraphs } } } From 26a30edec9f2d1c659d65b598a9bb9844c77040f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 21 Aug 2018 18:27:21 -0700 Subject: [PATCH 072/744] trying to fix loading bar --- interface/src/Application.h | 2 ++ .../src/octree/OctreePacketProcessor.cpp | 4 ++++ interface/src/octree/OctreePacketProcessor.h | 1 + interface/src/octree/SafeLanding.cpp | 18 +++++++++++++++ interface/src/octree/SafeLanding.h | 2 ++ .../scripting/WindowScriptingInterface.cpp | 12 ++-------- .../src/scripting/WindowScriptingInterface.h | 4 +--- scripts/system/interstitialPage.js | 23 ++++++------------- 8 files changed, 37 insertions(+), 29 deletions(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index 312920c4ac..db637f57e8 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -232,6 +232,8 @@ public: bool getPreferAvatarFingerOverStylus() { return false; } void setPreferAvatarFingerOverStylus(bool value); + float getDomainLoadProgress() { return _octreeProcessor.domainLoadProgress(); } + float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); } void setSettingConstrainToolbarPosition(bool setting); diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index 4bc6817a9e..11f6bbae13 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -137,3 +137,7 @@ void OctreePacketProcessor::startEntitySequence() { bool OctreePacketProcessor::isLoadSequenceComplete() const { return _safeLanding->isLoadSequenceComplete(); } + +float OctreePacketProcessor::domainLoadProgress() { + return _safeLanding->loadingProgressPercentage(); +} diff --git a/interface/src/octree/OctreePacketProcessor.h b/interface/src/octree/OctreePacketProcessor.h index f9c24ddc51..fb8f0b581a 100644 --- a/interface/src/octree/OctreePacketProcessor.h +++ b/interface/src/octree/OctreePacketProcessor.h @@ -27,6 +27,7 @@ public: void startEntitySequence(); bool isLoadSequenceComplete() const; + float domainLoadProgress(); signals: void packetVersionMismatch(); diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 31106457fb..8c0d51a344 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -53,6 +53,7 @@ void SafeLanding::startEntitySequence(QSharedPointer entityT void SafeLanding::stopEntitySequence() { Locker lock(_lock); _trackingEntities = false; + _maxTrackedEntityCount = 0; _initialStart = INVALID_SEQUENCE; _initialEnd = INVALID_SEQUENCE; _trackedEntities.clear(); @@ -75,6 +76,11 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { // Only track entities with downloaded collision bodies. _trackedEntities.emplace(entityID, entity); + int currentTrackedEntityCount = _trackedEntities.size(); + if (currentTrackedEntityCount > _maxTrackedEntityCount) { + _maxTrackedEntityCount = currentTrackedEntityCount; + } + qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); } } @@ -116,6 +122,18 @@ bool SafeLanding::isLoadSequenceComplete() { return !_trackingEntities; } +float SafeLanding::loadingProgressPercentage() { + float percentage = 0; + + if (_maxTrackedEntityCount != 0) { + int trackedEntityCount = _trackedEntities.size(); + percentage = (_maxTrackedEntityCount - trackedEntityCount) / _maxTrackedEntityCount; + } + + qDebug() << "----------> percentage: " << percentage << " <--------"; + return percentage; +} + bool SafeLanding::isSequenceNumbersComplete() { if (_initialStart != INVALID_SEQUENCE) { Locker lock(_lock); diff --git a/interface/src/octree/SafeLanding.h b/interface/src/octree/SafeLanding.h index 210dfbac25..8ef88a055e 100644 --- a/interface/src/octree/SafeLanding.h +++ b/interface/src/octree/SafeLanding.h @@ -29,6 +29,7 @@ public: void setCompletionSequenceNumbers(int first, int last); void noteReceivedsequenceNumber(int sequenceNumber); bool isLoadSequenceComplete(); + float loadingProgressPercentage(); private slots: void addTrackedEntity(const EntityItemID& entityID); @@ -49,6 +50,7 @@ private: static constexpr int INVALID_SEQUENCE = -1; int _initialStart { INVALID_SEQUENCE }; int _initialEnd { INVALID_SEQUENCE }; + int _maxTrackedEntityCount { 0 }; struct SequenceLessThan { bool operator()(const int& a, const int& b) const; diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 68be55f88a..f422a6a8fa 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -589,14 +589,6 @@ void WindowScriptingInterface::onMessageBoxSelected(int button) { } -int WindowScriptingInterface::getPhysicsNearbyEntitiesReadyCount() { - return qApp->getNearbyEntitiesReadyCount(); -} - -int WindowScriptingInterface::getPhysicsNearbyEntitiesStabilityCount() { - return qApp->getEntitiesStabilityCount(); -} - -int WindowScriptingInterface::getPhysicsNearbyEntitiesCount() { - return qApp->getNearbyEntitiesCount(); +float WindowScriptingInterface::domainLoadingProgress() { + return qApp->getDomainLoadProgress(); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 7b0b6435f7..3dcd8cfeed 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -561,9 +561,7 @@ public slots: */ void closeMessageBox(int id); - int getPhysicsNearbyEntitiesReadyCount(); - int getPhysicsNearbyEntitiesStabilityCount(); - int getPhysicsNearbyEntitiesCount(); + float domainLoadingProgress(); private slots: void onWindowGeometryChanged(const QRect& geometry); diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index dde3448b11..76d2a8b958 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -344,26 +344,17 @@ var deltaTime = (thisInterval - lastInterval); lastInterval = thisInterval; - var nearbyEntitiesReadyCount = Window.getPhysicsNearbyEntitiesReadyCount(); - var stabilityCount = Window.getPhysicsNearbyEntitiesStabilityCount(); - var nearbyEntitiesCount = Window.getPhysicsNearbyEntitiesCount(); + var domainLoadingProgressPercentage = Window.domainLoadingProgress(); - var stabilityPercentage = (stabilityCount / STABILITY); - if (stabilityPercentage > 1) { - stabilityPercentage = 1; - } - - var stabilityProgress = (MAX_X_SIZE * 0.75) * stabilityPercentage; - - var entitiesLoadedPercentage = 1; - if (nearbyEntitiesCount > 0) { - entitiesLoadedPercentage = nearbyEntitiesReadyCount / nearbyEntitiesCount; - } - var entitiesLoadedProgress = (MAX_X_SIZE * 0.25) * entitiesLoadedPercentage; - var progress = stabilityProgress + entitiesLoadedProgress; + var progress = MAX_X_SIZE * domainLoadingProgressPercentage; + print(progress); if (progress >= target) { target = progress; } + + if (physicsEnabled && target < MAX_X_SIZE) { + target = MAX_X_SIZE; + } currentProgress = lerp(currentProgress, target, 0.2); var properties = { localPosition: { x: (1.85 - (currentProgress / 2) - (-0.029 * (currentProgress / MAX_X_SIZE))), y: -0.935, z: 0.0 }, From d8d940b06a26d4eeb0a800c20dfa4cbdcf69b764 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 22 Aug 2018 10:28:32 -0700 Subject: [PATCH 073/744] Cap datagrams-at-once processed; debugging output --- libraries/networking/src/udt/Socket.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 090beb2726..5b64cc0716 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -319,7 +319,11 @@ void Socket::readPendingDatagrams() { int packetsRead = 0; int packetSizeWithHeader = -1; - while (_udpSocket.hasPendingDatagrams() && (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) { + // Max datagrams to read before processing: + static const int MAX_DATAGRAMS_CONSECUTIVELY = 10000; + while (_udpSocket.hasPendingDatagrams() + && (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1 + && packetsRead <= MAX_DATAGRAMS_CONSECUTIVELY) { // grab a time point we can mark as the receive time of this packet auto receiveTime = p_high_resolution_clock::now(); @@ -336,7 +340,6 @@ void Socket::readPendingDatagrams() { // we either didn't pull anything for this packet or there was an error reading (this seems to trigger // on windows even if there's not a packet available) - if (sizeRead < 0) { continue; } @@ -347,7 +350,10 @@ void Socket::readPendingDatagrams() { } - _maxDatagramsRead = std::max(_maxDatagramsRead, packetsRead); + if (packetsRead > _maxDatagramsRead) { + _maxDatagramsRead = packetsRead; + qCDebug(networking) << "readPendingDatagrams: Datagrams read:" << packetsRead; + } emit pendingDatagrams(packetsRead); } @@ -365,7 +371,7 @@ void Socket::processPendingDatagrams(int) { auto it = _unfilteredHandlers.find(senderSockAddr); if (it != _unfilteredHandlers.end()) { - // we have a registered unfiltered handler for this HifiSockAddr - call that and return + // we have a registered unfiltered handler for this HifiSockAddr (eg. STUN packet) - call that and return if (it->second) { auto basePacket = BasePacket::fromReceivedPacket(std::move(datagram._datagram), datagramSize, senderSockAddr); @@ -407,7 +413,7 @@ void Socket::processPendingDatagrams(int) { // save the sequence number in case this is the packet that sticks readyRead _lastReceivedSequenceNumber = packet->getSequenceNumber(); - // call our verification operator to see if this packet is verified + // call our hash verification operator to see if this packet is verified if (!_packetFilterOperator || _packetFilterOperator(*packet)) { if (packet->isReliable()) { // if this was a reliable packet then signal the matching connection with the sequence number From 54236e5c59ab8f33a52b1af381b1cbd31c2fdae1 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 22 Aug 2018 10:58:55 -0700 Subject: [PATCH 074/744] fixing loading bar --- interface/src/octree/SafeLanding.cpp | 42 +++++++++++++--------------- interface/src/octree/SafeLanding.h | 2 +- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 8c0d51a344..354a4046fa 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -66,23 +66,20 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { EntityItemPointer entity = _entityTree->findEntityByID(entityID); if (entity && !entity->getCollisionless()) { - const auto& entityType = entity->getType(); - if (entityType == EntityTypes::Model) { - ModelEntityItem * modelEntity = std::dynamic_pointer_cast(entity).get(); - static const std::set downloadedCollisionTypes - { SHAPE_TYPE_COMPOUND, SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH, SHAPE_TYPE_SIMPLE_HULL }; - bool hasAABox; - entity->getAABox(hasAABox); - if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { - // Only track entities with downloaded collision bodies. - _trackedEntities.emplace(entityID, entity); - int currentTrackedEntityCount = _trackedEntities.size(); - if (currentTrackedEntityCount > _maxTrackedEntityCount) { - _maxTrackedEntityCount = currentTrackedEntityCount; - } + static const std::set downloadedCollisionTypes + { SHAPE_TYPE_COMPOUND, SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH, SHAPE_TYPE_SIMPLE_HULL }; + bool hasAABox; + entity->getAABox(hasAABox); + if (hasAABox && downloadedCollisionTypes.count(entity->getShapeType()) != 0) { + // Only track entities with downloaded collision bodies. + _trackedEntities.emplace(entityID, entity); - qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); + float trackedEntityCount = (float)_trackedEntities.size(); + + if (trackedEntityCount > _maxTrackedEntityCount) { + _maxTrackedEntityCount = trackedEntityCount; } + qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); } } } @@ -123,15 +120,15 @@ bool SafeLanding::isLoadSequenceComplete() { } float SafeLanding::loadingProgressPercentage() { - float percentage = 0; - - if (_maxTrackedEntityCount != 0) { - int trackedEntityCount = _trackedEntities.size(); - percentage = (_maxTrackedEntityCount - trackedEntityCount) / _maxTrackedEntityCount; + Locker lock(_lock); + if (_maxTrackedEntityCount > 0) { + float trackedEntityCount = (float)_trackedEntities.size(); + qDebug() << "pocessed: " << (_maxTrackedEntityCount - trackedEntityCount) << " -> total: " << _maxTrackedEntityCount; + qDebug() << ((_maxTrackedEntityCount - trackedEntityCount) / _maxTrackedEntityCount); + return ((_maxTrackedEntityCount - trackedEntityCount) / _maxTrackedEntityCount); } - qDebug() << "----------> percentage: " << percentage << " <--------"; - return percentage; + return 0.0f; } bool SafeLanding::isSequenceNumbersComplete() { @@ -158,6 +155,7 @@ bool SafeLanding::isEntityPhysicsComplete() { auto entity = entityMapIter->second; if (!entity->shouldBePhysical() || entity->isReadyToComputeShape()) { entityMapIter = _trackedEntities.erase(entityMapIter); + qDebug() << "--> removing entity <--"; if (entityMapIter == _trackedEntities.end()) { break; } diff --git a/interface/src/octree/SafeLanding.h b/interface/src/octree/SafeLanding.h index 8ef88a055e..e1288f86ce 100644 --- a/interface/src/octree/SafeLanding.h +++ b/interface/src/octree/SafeLanding.h @@ -50,7 +50,7 @@ private: static constexpr int INVALID_SEQUENCE = -1; int _initialStart { INVALID_SEQUENCE }; int _initialEnd { INVALID_SEQUENCE }; - int _maxTrackedEntityCount { 0 }; + float _maxTrackedEntityCount { 0.0f }; struct SequenceLessThan { bool operator()(const int& a, const int& b) const; From e08d8e904651afe946a6400b3027aae9c6de09b9 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 22 Aug 2018 21:03:29 +0200 Subject: [PATCH 075/744] only show InteractiveWindow on top of interface window --- libraries/ui/src/InteractiveWindow.cpp | 30 ++++++++++++++++++++++++++ libraries/ui/src/InteractiveWindow.h | 4 ++++ libraries/ui/src/MainWindow.cpp | 13 +++++++++++ libraries/ui/src/MainWindow.h | 2 ++ libraries/ui/src/OffscreenUi.cpp | 16 ++------------ 5 files changed, 51 insertions(+), 14 deletions(-) diff --git a/libraries/ui/src/InteractiveWindow.cpp b/libraries/ui/src/InteractiveWindow.cpp index 5078fcb602..b4d5a068eb 100644 --- a/libraries/ui/src/InteractiveWindow.cpp +++ b/libraries/ui/src/InteractiveWindow.cpp @@ -13,12 +13,19 @@ #include #include +#include +#include #include #include #include "OffscreenUi.h" #include "shared/QtHelpers.h" +#include "MainWindow.h" + +#ifdef Q_OS_WIN +#include +#endif static auto CONTENT_WINDOW_QML = QUrl("InteractiveWindow.qml"); @@ -87,6 +94,11 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap connect(object, SIGNAL(windowClosed()), this, SIGNAL(closed()), Qt::QueuedConnection); connect(object, SIGNAL(selfDestruct()), this, SLOT(close()), Qt::QueuedConnection); +#ifdef Q_OS_WIN + connect(object, SIGNAL(nativeWindowChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); + connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); +#endif + QUrl sourceURL{ sourceUrl }; // If the passed URL doesn't correspond to a known scheme, assume it's a local file path if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) { @@ -279,6 +291,24 @@ int InteractiveWindow::getPresentationMode() const { return _qmlWindow->property(PRESENTATION_MODE_PROPERTY).toInt(); } +#ifdef Q_OS_WIN +void InteractiveWindow::parentNativeWindowToMainWindow() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "parentNativeWindowToMainWindow"); + return; + } + if (_qmlWindow.isNull()) { + return; + } + const auto nativeWindowProperty = _qmlWindow->property("nativeWindow"); + if (nativeWindowProperty.isNull() || !nativeWindowProperty.isValid()) { + return; + } + const auto nativeWindow = qvariant_cast(nativeWindowProperty); + SetWindowLongPtr((HWND)nativeWindow->winId(), GWLP_HWNDPARENT, (LONG)MainWindow::findMainWindow()->winId()); +} +#endif + void InteractiveWindow::setPresentationMode(int presentationMode) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setPresentationMode", Q_ARG(int, presentationMode)); diff --git a/libraries/ui/src/InteractiveWindow.h b/libraries/ui/src/InteractiveWindow.h index bf832550b5..f456b32e8d 100644 --- a/libraries/ui/src/InteractiveWindow.h +++ b/libraries/ui/src/InteractiveWindow.h @@ -84,6 +84,10 @@ private: Q_INVOKABLE void setPresentationMode(int presentationMode); Q_INVOKABLE int getPresentationMode() const; +#ifdef Q_OS_WIN + Q_INVOKABLE void parentNativeWindowToMainWindow(); +#endif + public slots: /**jsdoc diff --git a/libraries/ui/src/MainWindow.cpp b/libraries/ui/src/MainWindow.cpp index f9fc71e417..1a13194974 100644 --- a/libraries/ui/src/MainWindow.cpp +++ b/libraries/ui/src/MainWindow.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "ui/Logging.h" @@ -39,6 +40,18 @@ MainWindow::~MainWindow() { qCDebug(uiLogging) << "Destroying main window"; } +QWindow* MainWindow::findMainWindow() { + auto windows = qApp->topLevelWindows(); + QWindow* result = nullptr; + for (auto window : windows) { + if (window->objectName().contains("MainWindow")) { + result = window; + break; + } + } + return result; +} + void MainWindow::restoreGeometry() { // Did not use setGeometry() on purpose, // see http://doc.qt.io/qt-5/qsettings.html#restoring-the-state-of-a-gui-application diff --git a/libraries/ui/src/MainWindow.h b/libraries/ui/src/MainWindow.h index 75421340a2..fbd48e5eb1 100644 --- a/libraries/ui/src/MainWindow.h +++ b/libraries/ui/src/MainWindow.h @@ -21,6 +21,8 @@ class MainWindow : public QMainWindow { public: explicit MainWindow(QWidget* parent = NULL); ~MainWindow(); + + static QWindow* findMainWindow(); public slots: void restoreGeometry(); diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index a5ef1457db..d82cfbbf3f 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -29,6 +29,7 @@ #include "ui/Logging.h" #include +#include "MainWindow.h" /**jsdoc * @namespace OffscreenFlags @@ -649,20 +650,7 @@ public: } private: - - static QWindow* findMainWindow() { - auto windows = qApp->topLevelWindows(); - QWindow* result = nullptr; - for (auto window : windows) { - if (window->objectName().contains("MainWindow")) { - result = window; - break; - } - } - return result; - } - - QWindow* const _mainWindow { findMainWindow() }; + QWindow* const _mainWindow { MainWindow::findMainWindow() }; QWindow* _hackWindow { nullptr }; }; From 535fdf32cfbbb89df0775a6899e74c113ac4077e Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 22 Aug 2018 13:13:00 -0700 Subject: [PATCH 076/744] saving changes --- interface/src/octree/SafeLanding.cpp | 27 +++++++++++++++------------ interface/src/ui/OverlayConductor.cpp | 1 + scripts/system/interstitialPage.js | 20 ++++++++++++++++++++ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 354a4046fa..c1556b908d 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -66,20 +66,24 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { EntityItemPointer entity = _entityTree->findEntityByID(entityID); if (entity && !entity->getCollisionless()) { - static const std::set downloadedCollisionTypes - { SHAPE_TYPE_COMPOUND, SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH, SHAPE_TYPE_SIMPLE_HULL }; - bool hasAABox; - entity->getAABox(hasAABox); - if (hasAABox && downloadedCollisionTypes.count(entity->getShapeType()) != 0) { - // Only track entities with downloaded collision bodies. - _trackedEntities.emplace(entityID, entity); + const auto& entityType = entity->getType(); + if (entityType == EntityTypes::Model) { + ModelEntityItem * modelEntity = std::dynamic_pointer_cast(entity).get(); + static const std::set downloadedCollisionTypes + { SHAPE_TYPE_COMPOUND, SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH, SHAPE_TYPE_SIMPLE_HULL }; + bool hasAABox; + entity->getAABox(hasAABox); + if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { + // Only track entities with downloaded collision bodies. + _trackedEntities.emplace(entityID, entity); - float trackedEntityCount = (float)_trackedEntities.size(); + float trackedEntityCount = (float)_trackedEntities.size(); - if (trackedEntityCount > _maxTrackedEntityCount) { - _maxTrackedEntityCount = trackedEntityCount; + if (trackedEntityCount > _maxTrackedEntityCount) { + _maxTrackedEntityCount = trackedEntityCount; + } + qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); } - qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); } } } @@ -155,7 +159,6 @@ bool SafeLanding::isEntityPhysicsComplete() { auto entity = entityMapIter->second; if (!entity->shouldBePhysical() || entity->isReadyToComputeShape()) { entityMapIter = _trackedEntities.erase(entityMapIter); - qDebug() << "--> removing entity <--"; if (entityMapIter == _trackedEntities.end()) { break; } diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index e27001567f..398f9cf147 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -103,6 +103,7 @@ void OverlayConductor::update(float dt) { bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && !_suppressedByHead; if (targetVisible != currentVisible) { + qDebug() << "setting pinned: " << !targetVisible; offscreenUi->setPinned(!targetVisible); } if (shouldRecenter && !_suppressedByHead) { diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 76d2a8b958..ddfc8b3a32 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -207,6 +207,18 @@ return ((1 - t) * a + t * b); } + function resetValues() { + var properties = { + localPosition: { x: 1.85, y: -0.935, z: 0.0 }, + dimensions: { + x: 0.1, + y: 2.8 + } + }; + + Overlays.editOverlay(loadingBarProgress, properties); + } + function startInterstitialPage() { if (timer === null) { updateOverlays(Window.isPhysicsEnabled()); @@ -323,6 +335,10 @@ Overlays.editOverlay(loadingBarPlacard, properties); Overlays.editOverlay(loadingBarProgress, loadingBarProperties); + if (physicsEnabled) { + resetValues(); + } + Camera.mode = "first person"; } @@ -409,6 +425,10 @@ if (DEBUG) { tablet.removeButton(button); } + + renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = true; + renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = true; + renderViewTask.getConfig("LightingModel")["enablePointLight"] = true; } Script.scriptEnding.connect(cleanup); From 78458d62de4b6ee94ae1cd2673d1b1c8286584dd Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 22 Aug 2018 15:20:56 -0700 Subject: [PATCH 077/744] revert some changes and fix toolbar issue --- interface/src/Application.cpp | 3 ++- interface/src/octree/SafeLanding.cpp | 2 -- scripts/system/interstitialPage.js | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9d5a7a01f8..b437e7bd28 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3458,7 +3458,8 @@ void Application::setIsInterstitialMode(bool interstitialMode) { bool interstitialModeEnabled = menu->isOptionChecked("Enable Interstitial"); if (_interstitialMode != interstitialMode && interstitialModeEnabled) { _interstitialMode = interstitialMode; - menu->setIsOptionChecked(MenuOption::Overlays, !_interstitialMode); + + DependencyManager::get()->setPinned(_interstitialMode); emit interstitialModeChanged(_interstitialMode); } } diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index c1556b908d..d8d9a1720a 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -127,8 +127,6 @@ float SafeLanding::loadingProgressPercentage() { Locker lock(_lock); if (_maxTrackedEntityCount > 0) { float trackedEntityCount = (float)_trackedEntities.size(); - qDebug() << "pocessed: " << (_maxTrackedEntityCount - trackedEntityCount) << " -> total: " << _maxTrackedEntityCount; - qDebug() << ((_maxTrackedEntityCount - trackedEntityCount) / _maxTrackedEntityCount); return ((_maxTrackedEntityCount - trackedEntityCount) / _maxTrackedEntityCount); } diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index ddfc8b3a32..3f1fc58b55 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -27,6 +27,7 @@ var DEFAULT_Z_OFFSET = 5.45; var renderViewTask = Render.getConfig("RenderMainView"); + var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); var request = Script.require('request').request; var BUTTON_PROPERTIES = { text: "Interstitial" @@ -336,6 +337,7 @@ Overlays.editOverlay(loadingBarProgress, loadingBarProperties); if (physicsEnabled) { + toolbar.writeProperty("visible", true); resetValues(); } From 6dcce0e953038e6f248ee9d9ee7b40f1b16c5343 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 23 Aug 2018 10:59:23 +1200 Subject: [PATCH 078/744] Revert display test to depending just on hand to camera angle for now --- scripts/system/miniTablet.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 5c950cd594..17b1587604 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -409,8 +409,7 @@ handOrientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex)); cameraToHandDirection = Vec3.normalize(Vec3.subtract(handPosition, Camera.position)); - return Vec3.dot(cameraToHandDirection, Quat.getForward(handOrientation)) > MIN_HAND_CAMERA_ANGLE_COS - && Vec3.dot(cameraToHandDirection, Quat.getForward(Camera.orientation)) > MIN_HAND_CAMERA_ANGLE_COS; + return Vec3.dot(cameraToHandDirection, Quat.getForward(handOrientation)) > MIN_HAND_CAMERA_ANGLE_COS; } function enterProxyHidden() { From 335eb98bb77364d6c86736185adeb900f2d106a6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 23 Aug 2018 11:14:21 +1200 Subject: [PATCH 079/744] Increase size of the "..." button --- scripts/system/html/css/miniTablet.css | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/scripts/system/html/css/miniTablet.css b/scripts/system/html/css/miniTablet.css index 5ff9944a2c..75da2eaf82 100644 --- a/scripts/system/html/css/miniTablet.css +++ b/scripts/system/html/css/miniTablet.css @@ -80,23 +80,24 @@ footer { text-align: center; color: #e3e3e3; font-weight: bold; + font-size: 26px; } footer div { display: inline-block; - height: 12px; - width: 24px; + height: 14px; + width: 30px; position: relative; - top: 16px; + top: 14px; } footer div p { position: relative; - top: -8px; + top: -15px; } footer .button:hover { border: 1px solid #e3e3e3; - border-radius: 2px; - margin: -1px; + border-radius: 3px; + margin: -8px; } From db8e1bdfa34610031d9dd6ca8aa86bd15fb275af Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 22 Aug 2018 16:47:18 -0700 Subject: [PATCH 080/744] compile error fix --- interface/src/Application.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cc8736a481..6b2781417d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2258,8 +2258,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (_avatarOverrideUrl.isValid()) { getMyAvatar()->useFullAvatarURL(_avatarOverrideUrl); } - static const QUrl empty{}; - if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->cannonicalSkeletonModelURL(empty)) { + + if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->getSkeletonModelURL()) { getMyAvatar()->resetFullAvatarURL(); } getMyAvatar()->markIdentityDataChanged(); From bb83a2f620955a56d8ccdf30d218dc3066fe6bff Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 22 Aug 2018 16:48:10 -0700 Subject: [PATCH 081/744] interstitial script ending cleanup fixes --- scripts/system/interstitialPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 3f1fc58b55..1507c7bd9a 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -431,6 +431,7 @@ renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = true; renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = true; renderViewTask.getConfig("LightingModel")["enablePointLight"] = true; + toolbar.writeProperty("visible", true); } Script.scriptEnding.connect(cleanup); From 8c422cb0ebf6f29ff90682eb1431f5b44357b717 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 22 Aug 2018 16:54:59 -0700 Subject: [PATCH 082/744] change corner cubes to center cube, replace spheres with cubes --- .../system/libraries/entitySelectionTool.js | 362 +++++++----------- 1 file changed, 134 insertions(+), 228 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 1c7d5244a1..bc36024e07 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -433,14 +433,14 @@ SelectionDisplay = (function() { var ROTATE_DISPLAY_SIZE_Y_MULTIPLIER = 0.09; var ROTATE_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.07; - var STRETCH_SPHERE_OFFSET = 0.06; - var STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE = 0.01; + var STRETCH_CUBE_OFFSET = 0.06; + var STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.02; var STRETCH_MINIMUM_DIMENSION = 0.001; var STRETCH_ALL_MINIMUM_DIMENSION = 0.01; var STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE = 6; var STRETCH_PANEL_WIDTH = 0.01; - var SCALE_CUBE_OFFSET = 0.5; + var SCALE_EDGE_OFFSET = 0.5; var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.0125; var CLONER_OFFSET = { x: 0.9, y: -0.9, z: 0.9 }; @@ -589,20 +589,18 @@ SelectionDisplay = (function() { leftMargin: 0 }); - var handlePropertiesStretchSpheres = { - alpha: 1, - shape: "Sphere", + var handlePropertiesStretchCubes = { solid: true, visible: false, ignoreRayIntersection: false, drawInFront: true }; - var handleStretchXSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchXSphere, { color: COLOR_RED }); - var handleStretchYSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchYSphere, { color: COLOR_GREEN }); - var handleStretchZSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchZSphere, { color: COLOR_BLUE }); + var handleStretchXCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchXCube, { color: COLOR_RED }); + var handleStretchYCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchYCube, { color: COLOR_GREEN }); + var handleStretchZCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchZCube, { color: COLOR_BLUE }); var handlePropertiesStretchPanel = { shape: "Quad", @@ -619,8 +617,7 @@ SelectionDisplay = (function() { var handleStretchZPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); Overlays.editOverlay(handleStretchZPanel, { color: COLOR_BLUE }); - var handlePropertiesScaleCubes = { - alpha: 1, + var handleScaleCube = Overlays.addOverlay("cube", { size: 0.025, color: COLOR_SCALE_CUBE, solid: true, @@ -628,15 +625,7 @@ SelectionDisplay = (function() { ignoreRayIntersection: false, drawInFront: true, borderSize: 1.4 - }; - var handleScaleLBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, -z) - var handleScaleRBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, -z) - var handleScaleLBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, z) - var handleScaleRBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, z) - var handleScaleLTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, -z) - var handleScaleRTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, -z) - var handleScaleLTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, z) - var handleScaleRTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, z) + }); var handlePropertiesScaleEdge = { alpha: 1, @@ -738,20 +727,13 @@ SelectionDisplay = (function() { handleRotateRollRing, handleRotateCurrentRing, rotationDegreesDisplay, - handleStretchXSphere, - handleStretchYSphere, - handleStretchZSphere, + handleStretchXCube, + handleStretchYCube, + handleStretchZCube, handleStretchXPanel, handleStretchYPanel, handleStretchZPanel, - handleScaleLBNCube, - handleScaleRBNCube, - handleScaleLBFCube, - handleScaleRBFCube, - handleScaleLTNCube, - handleScaleRTNCube, - handleScaleLTFCube, - handleScaleRTFCube, + handleScaleCube, handleScaleTREdge, handleScaleTLEdge, handleScaleTFEdge, @@ -787,21 +769,14 @@ SelectionDisplay = (function() { overlayNames[handleRotateCurrentRing] = "handleRotateCurrentRing"; overlayNames[rotationDegreesDisplay] = "rotationDegreesDisplay"; - overlayNames[handleStretchXSphere] = "handleStretchXSphere"; - overlayNames[handleStretchYSphere] = "handleStretchYSphere"; - overlayNames[handleStretchZSphere] = "handleStretchZSphere"; + overlayNames[handleStretchXCube] = "handleStretchXCube"; + overlayNames[handleStretchYCube] = "handleStretchYCube"; + overlayNames[handleStretchZCube] = "handleStretchZCube"; overlayNames[handleStretchXPanel] = "handleStretchXPanel"; overlayNames[handleStretchYPanel] = "handleStretchYPanel"; overlayNames[handleStretchZPanel] = "handleStretchZPanel"; - overlayNames[handleScaleLBNCube] = "handleScaleLBNCube"; - overlayNames[handleScaleRBNCube] = "handleScaleRBNCube"; - overlayNames[handleScaleLBFCube] = "handleScaleLBFCube"; - overlayNames[handleScaleRBFCube] = "handleScaleRBFCube"; - overlayNames[handleScaleLTNCube] = "handleScaleLTNCube"; - overlayNames[handleScaleRTNCube] = "handleScaleRTNCube"; - overlayNames[handleScaleLTFCube] = "handleScaleLTFCube"; - overlayNames[handleScaleRTFCube] = "handleScaleRTFCube"; + overlayNames[handleScaleCube] = "handleScaleCube"; overlayNames[handleScaleTREdge] = "handleScaleTREdge"; overlayNames[handleScaleTLEdge] = "handleScaleTLEdge"; @@ -1021,32 +996,25 @@ SelectionDisplay = (function() { case handleTranslateXCone: case handleTranslateXCylinder: case handleRotatePitchRing: - case handleStretchXSphere: + case handleStretchXCube: pickedColor = COLOR_RED; highlightNeeded = true; break; case handleTranslateYCone: case handleTranslateYCylinder: case handleRotateYawRing: - case handleStretchYSphere: + case handleStretchYCube: pickedColor = COLOR_GREEN; highlightNeeded = true; break; case handleTranslateZCone: case handleTranslateZCylinder: case handleRotateRollRing: - case handleStretchZSphere: + case handleStretchZCube: pickedColor = COLOR_BLUE; highlightNeeded = true; break; - case handleScaleLBNCube: - case handleScaleRBNCube: - case handleScaleLBFCube: - case handleScaleRBFCube: - case handleScaleLTNCube: - case handleScaleRTNCube: - case handleScaleLTFCube: - case handleScaleRTFCube: + case handleScaleCube: pickedColor = COLOR_SCALE_CUBE; highlightNeeded = true; break; @@ -1424,127 +1392,83 @@ SelectionDisplay = (function() { dimensions: arrowConeDimensions }); - // UPDATE SCALE CUBES - var scaleCubeOffsetX = SCALE_CUBE_OFFSET * dimensions.x; - var scaleCubeOffsetY = SCALE_CUBE_OFFSET * dimensions.y; - var scaleCubeOffsetZ = SCALE_CUBE_OFFSET * dimensions.z; - var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; - var scaleLBNCubePosition = { x: -scaleCubeOffsetX, y: -scaleCubeOffsetY, z: -scaleCubeOffsetZ }; - scaleLBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBNCubePosition)); - var scaleLBNCubeToCamera = getDistanceToCamera(scaleLBNCubePosition); - var scaleRBNCubePosition = { x: scaleCubeOffsetX, y: -scaleCubeOffsetY, z: -scaleCubeOffsetZ }; - scaleRBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBNCubePosition)); - var scaleRBNCubeToCamera = getDistanceToCamera(scaleRBNCubePosition); - var scaleLBFCubePosition = { x: -scaleCubeOffsetX, y: -scaleCubeOffsetY, z: scaleCubeOffsetZ }; - scaleLBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBFCubePosition)); - var scaleLBFCubeToCamera = getDistanceToCamera(scaleLBFCubePosition); - var scaleRBFCubePosition = { x: scaleCubeOffsetX, y: -scaleCubeOffsetY, z: scaleCubeOffsetZ }; - scaleRBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBFCubePosition)); - var scaleRBFCubeToCamera = getDistanceToCamera(scaleRBFCubePosition); - var scaleLTNCubePosition = { x: -scaleCubeOffsetX, y: scaleCubeOffsetY, z: -scaleCubeOffsetZ }; - scaleLTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTNCubePosition)); - var scaleLTNCubeToCamera = getDistanceToCamera(scaleLTNCubePosition); - var scaleRTNCubePosition = { x: scaleCubeOffsetX, y: scaleCubeOffsetY, z: -scaleCubeOffsetZ }; - scaleRTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTNCubePosition)); - var scaleRTNCubeToCamera = getDistanceToCamera(scaleRTNCubePosition); - var scaleLTFCubePosition = { x: -scaleCubeOffsetX, y: scaleCubeOffsetY, z: scaleCubeOffsetZ }; - scaleLTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTFCubePosition)); - var scaleLTFCubeToCamera = getDistanceToCamera(scaleLTFCubePosition); - var scaleRTFCubePosition = { x: scaleCubeOffsetX, y: scaleCubeOffsetY, z: scaleCubeOffsetZ }; - scaleRTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTFCubePosition)); - var scaleRTFCubeToCamera = getDistanceToCamera(scaleRTFCubePosition); - - var scaleCubeToCamera = Math.min(scaleLBNCubeToCamera, scaleRBNCubeToCamera, scaleLBFCubeToCamera, - scaleRBFCubeToCamera, scaleLTNCubeToCamera, scaleRTNCubeToCamera, - scaleLTFCubeToCamera, scaleRTFCubeToCamera); - var scaleCubeDimension = scaleCubeToCamera * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE; + // UPDATE SCALE CUBE + var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; + var scaleCubeDimension = rotateDimension * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE / + ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; var scaleCubeDimensions = { x: scaleCubeDimension, y: scaleCubeDimension, z: scaleCubeDimension }; - - Overlays.editOverlay(handleScaleLBNCube, { - position: scaleLBNCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleRBNCube, { - position: scaleRBNCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleLBFCube, { - position: scaleLBFCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleRBFCube, { - position: scaleRBFCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleLTNCube, { - position: scaleLTNCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleRTNCube, { - position: scaleRTNCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleLTFCube, { - position: scaleLTFCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleRTFCube, { - position: scaleRTFCubePosition, + Overlays.editOverlay(handleScaleCube, { + position: position, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); // UPDATE SCALE EDGES - Overlays.editOverlay(handleScaleTREdge, { start: scaleRTNCubePosition, end: scaleRTFCubePosition }); - Overlays.editOverlay(handleScaleTLEdge, { start: scaleLTNCubePosition, end: scaleLTFCubePosition }); - Overlays.editOverlay(handleScaleTFEdge, { start: scaleLTFCubePosition, end: scaleRTFCubePosition }); - Overlays.editOverlay(handleScaleTNEdge, { start: scaleLTNCubePosition, end: scaleRTNCubePosition }); - Overlays.editOverlay(handleScaleBREdge, { start: scaleRBNCubePosition, end: scaleRBFCubePosition }); - Overlays.editOverlay(handleScaleBLEdge, { start: scaleLBNCubePosition, end: scaleLBFCubePosition }); - Overlays.editOverlay(handleScaleBFEdge, { start: scaleLBFCubePosition, end: scaleRBFCubePosition }); - Overlays.editOverlay(handleScaleBNEdge, { start: scaleLBNCubePosition, end: scaleRBNCubePosition }); - Overlays.editOverlay(handleScaleNREdge, { start: scaleRTNCubePosition, end: scaleRBNCubePosition }); - Overlays.editOverlay(handleScaleNLEdge, { start: scaleLTNCubePosition, end: scaleLBNCubePosition }); - Overlays.editOverlay(handleScaleFREdge, { start: scaleRTFCubePosition, end: scaleRBFCubePosition }); - Overlays.editOverlay(handleScaleFLEdge, { start: scaleLTFCubePosition, end: scaleLBFCubePosition }); + var edgeOffsetX = SCALE_EDGE_OFFSET * dimensions.x; + var edgeOffsetY = SCALE_EDGE_OFFSET * dimensions.y; + var edgeOffsetZ = SCALE_EDGE_OFFSET * dimensions.z; + var LBNPosition = { x: -edgeOffsetX, y: -edgeOffsetY, z: -edgeOffsetZ }; + LBNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LBNPosition)); + var RBNPosition = { x: edgeOffsetX, y: -edgeOffsetY, z: -edgeOffsetZ }; + RBNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RBNPosition)); + var LBFPosition = { x: -edgeOffsetX, y: -edgeOffsetY, z: edgeOffsetZ }; + LBFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LBFPosition)); + var RBFPosition = { x: edgeOffsetX, y: -edgeOffsetY, z: edgeOffsetZ }; + RBFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RBFPosition)); + var LTNPosition = { x: -edgeOffsetX, y: edgeOffsetY, z: -edgeOffsetZ }; + LTNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LTNPosition)); + var RTNPosition = { x: edgeOffsetX, y: edgeOffsetY, z: -edgeOffsetZ }; + RTNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTNPosition)); + var LTFPosition = { x: -edgeOffsetX, y: edgeOffsetY, z: edgeOffsetZ }; + LTFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LTFPosition)); + var RTFPosition = { x: edgeOffsetX, y: edgeOffsetY, z: edgeOffsetZ }; + RTFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTFPosition)); + Overlays.editOverlay(handleScaleTREdge, { start: RTNPosition, end: RTFPosition }); + Overlays.editOverlay(handleScaleTLEdge, { start: LTNPosition, end: LTFPosition }); + Overlays.editOverlay(handleScaleTFEdge, { start: LTFPosition, end: RTFPosition }); + Overlays.editOverlay(handleScaleTNEdge, { start: LTNPosition, end: RTNPosition }); + Overlays.editOverlay(handleScaleBREdge, { start: RBNPosition, end: RBFPosition }); + Overlays.editOverlay(handleScaleBLEdge, { start: LBNPosition, end: LBFPosition }); + Overlays.editOverlay(handleScaleBFEdge, { start: LBFPosition, end: RBFPosition }); + Overlays.editOverlay(handleScaleBNEdge, { start: LBNPosition, end: RBNPosition }); + Overlays.editOverlay(handleScaleNREdge, { start: RTNPosition, end: RBNPosition }); + Overlays.editOverlay(handleScaleNLEdge, { start: LTNPosition, end: LBNPosition }); + Overlays.editOverlay(handleScaleFREdge, { start: RTFPosition, end: RBFPosition }); + Overlays.editOverlay(handleScaleFLEdge, { start: LTFPosition, end: LBFPosition }); - // UPDATE STRETCH SPHERES - var stretchSphereDimension = rotateDimension * STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE / + // UPDATE STRETCH CUBES + var stretchCubeDimension = rotateDimension * STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var stretchSphereDimensions = { x: stretchSphereDimension, y: stretchSphereDimension, z: stretchSphereDimension }; - var stretchSphereOffset = rotateDimension * STRETCH_SPHERE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var stretchXPosition = { x: stretchSphereOffset, y: 0, z: 0 }; + var stretchCubeDimensions = { x: stretchCubeDimension, y: stretchCubeDimension, z: stretchCubeDimension }; + var stretchCubeOffset = rotateDimension * STRETCH_CUBE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var stretchXPosition = { x: stretchCubeOffset, y: 0, z: 0 }; stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); - Overlays.editOverlay(handleStretchXSphere, { + Overlays.editOverlay(handleStretchXCube, { position: stretchXPosition, - dimensions: stretchSphereDimensions + rotation: rotationX, + dimensions: stretchCubeDimensions }); - var stretchYPosition = { x: 0, y: stretchSphereOffset, z: 0 }; + var stretchYPosition = { x: 0, y: stretchCubeOffset, z: 0 }; stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); - Overlays.editOverlay(handleStretchYSphere, { + Overlays.editOverlay(handleStretchYCube, { position: stretchYPosition, - dimensions: stretchSphereDimensions + rotation: rotationY, + dimensions: stretchCubeDimensions }); - var stretchZPosition = { x: 0, y: 0, z: stretchSphereOffset }; + var stretchZPosition = { x: 0, y: 0, z: stretchCubeOffset }; stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); - Overlays.editOverlay(handleStretchZSphere, { - position: stretchZPosition, - dimensions: stretchSphereDimensions + Overlays.editOverlay(handleStretchZCube, { + position: stretchZPosition, + rotation: rotationZ, + dimensions: stretchCubeDimensions }); // UPDATE STRETCH HIGHLIGHT PANELS - var scaleRBFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRBFCubePosition); - var scaleRTFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTFCubePosition); - var scaleLTNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleLTNCubePosition); - var scaleRTNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTNCubePosition); - var stretchPanelXDimensions = Vec3.subtract(scaleRTNCubePositionRotated, scaleRBFCubePositionRotated); + var RBFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RBFPosition); + var RTFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RTFPosition); + var LTNPositionRotated = Vec3.multiplyQbyV(rotationInverse, LTNPosition); + var RTNPositionRotated = Vec3.multiplyQbyV(rotationInverse, RTNPosition); + var stretchPanelXDimensions = Vec3.subtract(RTNPositionRotated, RBFPositionRotated); var tempY = Math.abs(stretchPanelXDimensions.y); stretchPanelXDimensions.x = STRETCH_PANEL_WIDTH; stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z); @@ -1555,7 +1479,7 @@ SelectionDisplay = (function() { rotation: rotationZ, dimensions: stretchPanelXDimensions }); - var stretchPanelYDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRTFCubePositionRotated); + var stretchPanelYDimensions = Vec3.subtract(LTNPositionRotated, RTFPositionRotated); var tempX = Math.abs(stretchPanelYDimensions.x); stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); stretchPanelYDimensions.y = STRETCH_PANEL_WIDTH; @@ -1566,7 +1490,7 @@ SelectionDisplay = (function() { rotation: rotationY, dimensions: stretchPanelYDimensions }); - var stretchPanelZDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRBFCubePositionRotated); + var stretchPanelZDimensions = Vec3.subtract(LTNPositionRotated, RBFPositionRotated); tempX = Math.abs(stretchPanelZDimensions.x); stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); stretchPanelZDimensions.y = tempX; @@ -1622,15 +1546,10 @@ SelectionDisplay = (function() { that.setHandleRotateRollVisible(!activeTool || isActiveTool(handleRotateRollRing)); var showScaleStretch = !activeTool && SelectionManager.selections.length === 1 && spaceMode === SPACE_LOCAL; - that.setHandleStretchXVisible(showScaleStretch || isActiveTool(handleStretchXSphere)); - that.setHandleStretchYVisible(showScaleStretch || isActiveTool(handleStretchYSphere)); - that.setHandleStretchZVisible(showScaleStretch || isActiveTool(handleStretchZSphere)); - that.setHandleScaleCubeVisible(showScaleStretch || isActiveTool(handleScaleLBNCube) || - isActiveTool(handleScaleRBNCube) || isActiveTool(handleScaleLBFCube) || - isActiveTool(handleScaleRBFCube) || isActiveTool(handleScaleLTNCube) || - isActiveTool(handleScaleRTNCube) || isActiveTool(handleScaleLTFCube) || - isActiveTool(handleScaleRTFCube) || isActiveTool(handleStretchXSphere) || - isActiveTool(handleStretchYSphere) || isActiveTool(handleStretchZSphere)); + that.setHandleStretchXVisible(showScaleStretch || isActiveTool(handleStretchXCube)); + that.setHandleStretchYVisible(showScaleStretch || isActiveTool(handleStretchYCube)); + that.setHandleStretchZVisible(showScaleStretch || isActiveTool(handleStretchZCube)); + that.setHandleScaleCubeVisible(showScaleStretch || isActiveTool(handleScaleCube)); var showOutlineForZone = (SelectionManager.selections.length === 1 && typeof SelectionManager.savedProperties[SelectionManager.selections[0]] !== "undefined" && @@ -1721,15 +1640,15 @@ SelectionDisplay = (function() { }; that.setHandleStretchXVisible = function(isVisible) { - Overlays.editOverlay(handleStretchXSphere, { visible: isVisible }); + Overlays.editOverlay(handleStretchXCube, { visible: isVisible }); }; that.setHandleStretchYVisible = function(isVisible) { - Overlays.editOverlay(handleStretchYSphere, { visible: isVisible }); + Overlays.editOverlay(handleStretchYCube, { visible: isVisible }); }; that.setHandleStretchZVisible = function(isVisible) { - Overlays.editOverlay(handleStretchZSphere, { visible: isVisible }); + Overlays.editOverlay(handleStretchZCube, { visible: isVisible }); }; // FUNCTION: SET HANDLE SCALE VISIBLE @@ -1739,14 +1658,7 @@ SelectionDisplay = (function() { }; that.setHandleScaleCubeVisible = function(isVisible) { - Overlays.editOverlay(handleScaleLBNCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleRBNCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleLBFCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleRBFCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleLTNCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleRTNCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleLTFCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleRTFCube, { visible: isVisible }); + Overlays.editOverlay(handleScaleCube, { visible: isVisible }); }; that.setHandleScaleEdgeVisible = function(isVisible) { @@ -2157,6 +2069,8 @@ SelectionDisplay = (function() { var previousPickRay = null; var onBegin = function(event, pickRay, pickResult) { + var proportional = directionEnum === STRETCH_DIRECTION.ALL; + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); initialProperties = properties; rotation = (spaceMode === SPACE_LOCAL) ? properties.rotation : Quat.IDENTITY; @@ -2244,9 +2158,17 @@ SelectionDisplay = (function() { } planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); - lastPick = rayPlaneIntersection(pickRay, - pickRayPosition, - planeNormal); + + if (proportional) { + lastPick = pickRay.origin; + } else { + lastPick = rayPlaneIntersection(pickRay, + pickRayPosition, + planeNormal); + } + + Vec3.print("DBACK TEST begin pickRayPosition ", pickRayPosition); + Vec3.print("DBACK TEST begin lastPick ", lastPick); var planeNormal3D = { x: 0, @@ -2351,7 +2273,23 @@ SelectionDisplay = (function() { vector = grid.snapToSpacing(vector); var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector)); - if (directionEnum === STRETCH_DIRECTION.ALL) { + + if (proportional) { + newPick = pickRay.origin; + var pickDifference = Vec3.subtract(newPick, lastPick); + Vec3.print("DBACK TEST move newPick ", newPick); + Vec3.print("DBACK TEST move pickDifference ", pickDifference); + changeInDimensions = + } + + //Vec3.print("DBACK TEST move pickRay.origin ", pickRay.origin); + //Vec3.print("DBACK TEST move pickRay.direction ", pickRay.direction); + //Vec3.print("DBACK TEST move newPick ", newPick); + //Vec3.print("DBACK TEST move vector ", vector); + //Vec3.print("DBACK TEST move changeInDimensions ", changeInDimensions); + + + if (proportional) { var toCameraDistance = getDistanceToCamera(position); var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); @@ -2393,9 +2331,9 @@ SelectionDisplay = (function() { newDimensions.z = minimumDimension; changeInDimensions.z = minimumDimension - initialDimensions.z; } - + var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); - if (directionEnum === STRETCH_DIRECTION.ALL) { + if (proportional) { changeInPosition = { x: 0, y: 0, z: 0 }; } var newPosition = Vec3.sum(initialPosition, changeInPosition); @@ -2446,35 +2384,10 @@ SelectionDisplay = (function() { } // TOOL DEFINITION: HANDLE SCALE TOOL - function addHandleScaleTool(overlay, mode, directionEnum) { - var directionVector, offset, selectedHandle; - if (directionEnum === SCALE_DIRECTION.LBN) { - directionVector = { x: 1, y: 1, z: 1 }; - selectedHandle = handleScaleLBNCube; - } else if (directionEnum === SCALE_DIRECTION.RBN) { - directionVector = { x: -1, y: 1, z: 1 }; - selectedHandle = handleScaleRBNCube; - } else if (directionEnum === SCALE_DIRECTION.LBF) { - directionVector = { x: 1, y: 1, z: -1 }; - selectedHandle = handleScaleLBFCube; - } else if (directionEnum === SCALE_DIRECTION.RBF) { - directionVector = { x: -1, y: 1, z: -1 }; - selectedHandle = handleScaleRBFCube; - } else if (directionEnum === SCALE_DIRECTION.LTN) { - directionVector = { x: 1, y: -1, z: 1 }; - selectedHandle = handleScaleLTNCube; - } else if (directionEnum === SCALE_DIRECTION.RTN) { - directionVector = { x: -1, y: -1, z: 1 }; - selectedHandle = handleScaleRTNCube; - } else if (directionEnum === SCALE_DIRECTION.LTF) { - directionVector = { x: 1, y: -1, z: -1 }; - selectedHandle = handleScaleLTFCube; - } else if (directionEnum === SCALE_DIRECTION.RTF) { - directionVector = { x: -1, y: -1, z: -1 }; - selectedHandle = handleScaleRTFCube; - } - offset = Vec3.multiply(directionVector, NEGATE_VECTOR); - var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVector, directionVector, offset, null, selectedHandle); + function addHandleScaleTool(overlay, mode) { + var directionVector = { x:0, y:0, z:0 }; + var offset = { x:0, y:0, z:0 }; + var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVector, directionVector, offset, null, handleScaleCube); return addHandleTool(overlay, tool); } @@ -2747,18 +2660,11 @@ SelectionDisplay = (function() { addHandleRotateTool(handleRotateYawRing, "ROTATE_YAW", ROTATE_DIRECTION.YAW); addHandleRotateTool(handleRotateRollRing, "ROTATE_ROLL", ROTATE_DIRECTION.ROLL); - addHandleStretchTool(handleStretchXSphere, "STRETCH_X", STRETCH_DIRECTION.X); - addHandleStretchTool(handleStretchYSphere, "STRETCH_Y", STRETCH_DIRECTION.Y); - addHandleStretchTool(handleStretchZSphere, "STRETCH_Z", STRETCH_DIRECTION.Z); - - addHandleScaleTool(handleScaleLBNCube, "SCALE_LBN", SCALE_DIRECTION.LBN); - addHandleScaleTool(handleScaleRBNCube, "SCALE_RBN", SCALE_DIRECTION.RBN); - addHandleScaleTool(handleScaleLBFCube, "SCALE_LBF", SCALE_DIRECTION.LBF); - addHandleScaleTool(handleScaleRBFCube, "SCALE_RBF", SCALE_DIRECTION.RBF); - addHandleScaleTool(handleScaleLTNCube, "SCALE_LTN", SCALE_DIRECTION.LTN); - addHandleScaleTool(handleScaleRTNCube, "SCALE_RTN", SCALE_DIRECTION.RTN); - addHandleScaleTool(handleScaleLTFCube, "SCALE_LTF", SCALE_DIRECTION.LTF); - addHandleScaleTool(handleScaleRTFCube, "SCALE_RTF", SCALE_DIRECTION.RTF); + addHandleStretchTool(handleStretchXCube, "STRETCH_X", STRETCH_DIRECTION.X); + addHandleStretchTool(handleStretchYCube, "STRETCH_Y", STRETCH_DIRECTION.Y); + addHandleStretchTool(handleStretchZCube, "STRETCH_Z", STRETCH_DIRECTION.Z); + addHandleScaleTool(handleScaleCube, "SCALE"); + return that; }()); From 8eddb36c825acb3f6e5fffdc861bb6dc89bb7f31 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 22 Aug 2018 17:29:40 -0700 Subject: [PATCH 083/744] resize scale cube --- .../system/libraries/entitySelectionTool.js | 70 +++++++------------ 1 file changed, 25 insertions(+), 45 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index bc36024e07..f251a1611c 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -441,7 +441,7 @@ SelectionDisplay = (function() { var STRETCH_PANEL_WIDTH = 0.01; var SCALE_EDGE_OFFSET = 0.5; - var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.0125; + var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.02; var CLONER_OFFSET = { x: 0.9, y: -0.9, z: 0.9 }; @@ -617,7 +617,7 @@ SelectionDisplay = (function() { var handleStretchZPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); Overlays.editOverlay(handleStretchZPanel, { color: COLOR_BLUE }); - var handleScaleCube = Overlays.addOverlay("cube", { + var handleScaleCube = Overlays.addOverlay("cube", { size: 0.025, color: COLOR_SCALE_CUBE, solid: true, @@ -1393,8 +1393,8 @@ SelectionDisplay = (function() { }); // UPDATE SCALE CUBE - var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; - var scaleCubeDimension = rotateDimension * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE / + var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; + var scaleCubeDimension = rotateDimension * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; var scaleCubeDimensions = { x: scaleCubeDimension, y: scaleCubeDimension, z: scaleCubeDimension }; Overlays.editOverlay(handleScaleCube, { @@ -1404,10 +1404,10 @@ SelectionDisplay = (function() { }); // UPDATE SCALE EDGES - var edgeOffsetX = SCALE_EDGE_OFFSET * dimensions.x; - var edgeOffsetY = SCALE_EDGE_OFFSET * dimensions.y; - var edgeOffsetZ = SCALE_EDGE_OFFSET * dimensions.z; - var LBNPosition = { x: -edgeOffsetX, y: -edgeOffsetY, z: -edgeOffsetZ }; + var edgeOffsetX = SCALE_EDGE_OFFSET * dimensions.x; + var edgeOffsetY = SCALE_EDGE_OFFSET * dimensions.y; + var edgeOffsetZ = SCALE_EDGE_OFFSET * dimensions.z; + var LBNPosition = { x: -edgeOffsetX, y: -edgeOffsetY, z: -edgeOffsetZ }; LBNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LBNPosition)); var RBNPosition = { x: edgeOffsetX, y: -edgeOffsetY, z: -edgeOffsetZ }; RBNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RBNPosition)); @@ -1445,21 +1445,21 @@ SelectionDisplay = (function() { stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); Overlays.editOverlay(handleStretchXCube, { position: stretchXPosition, - rotation: rotationX, + rotation: rotationX, dimensions: stretchCubeDimensions }); var stretchYPosition = { x: 0, y: stretchCubeOffset, z: 0 }; stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); Overlays.editOverlay(handleStretchYCube, { position: stretchYPosition, - rotation: rotationY, + rotation: rotationY, dimensions: stretchCubeDimensions }); var stretchZPosition = { x: 0, y: 0, z: stretchCubeOffset }; stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); Overlays.editOverlay(handleStretchZCube, { position: stretchZPosition, - rotation: rotationZ, + rotation: rotationZ, dimensions: stretchCubeDimensions }); @@ -2067,10 +2067,9 @@ SelectionDisplay = (function() { var pickRayPosition3D = null; var rotation = null; var previousPickRay = null; + var beginMouseEvent = null; - var onBegin = function(event, pickRay, pickResult) { - var proportional = directionEnum === STRETCH_DIRECTION.ALL; - + var onBegin = function(event, pickRay, pickResult) { var properties = Entities.getEntityProperties(SelectionManager.selections[0]); initialProperties = properties; rotation = (spaceMode === SPACE_LOCAL) ? properties.rotation : Quat.IDENTITY; @@ -2158,17 +2157,7 @@ SelectionDisplay = (function() { } planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); - - if (proportional) { - lastPick = pickRay.origin; - } else { - lastPick = rayPlaneIntersection(pickRay, - pickRayPosition, - planeNormal); - } - - Vec3.print("DBACK TEST begin pickRayPosition ", pickRayPosition); - Vec3.print("DBACK TEST begin lastPick ", lastPick); + lastPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); var planeNormal3D = { x: 0, @@ -2208,6 +2197,7 @@ SelectionDisplay = (function() { } previousPickRay = pickRay; + beginMouseEvent = event; }; var onEnd = function(event, reason) { @@ -2273,23 +2263,13 @@ SelectionDisplay = (function() { vector = grid.snapToSpacing(vector); var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector)); - - if (proportional) { - newPick = pickRay.origin; - var pickDifference = Vec3.subtract(newPick, lastPick); - Vec3.print("DBACK TEST move newPick ", newPick); - Vec3.print("DBACK TEST move pickDifference ", pickDifference); - changeInDimensions = - } - - //Vec3.print("DBACK TEST move pickRay.origin ", pickRay.origin); - //Vec3.print("DBACK TEST move pickRay.direction ", pickRay.direction); - //Vec3.print("DBACK TEST move newPick ", newPick); - //Vec3.print("DBACK TEST move vector ", vector); - //Vec3.print("DBACK TEST move changeInDimensions ", changeInDimensions); - - - if (proportional) { + + if (proportional) { + var viewportDimensions = Controller.getViewportDimensions(); + var mouseXDifference = (event.x - beginMouseEvent.x) / viewportDimensions.x; + var mouseYDifference = (beginMouseEvent.y - event.y) / viewportDimensions.y; + var mouseDifference = mouseXDifference + mouseYDifference; + changeInDimensions = { x:mouseDifference, y:mouseDifference, z:mouseDifference }; var toCameraDistance = getDistanceToCamera(position); var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); @@ -2385,8 +2365,8 @@ SelectionDisplay = (function() { // TOOL DEFINITION: HANDLE SCALE TOOL function addHandleScaleTool(overlay, mode) { - var directionVector = { x:0, y:0, z:0 }; - var offset = { x:0, y:0, z:0 }; + var directionVector = { x:0, y:0, z:0 }; + var offset = { x:0, y:0, z:0 }; var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVector, directionVector, offset, null, handleScaleCube); return addHandleTool(overlay, tool); } @@ -2665,6 +2645,6 @@ SelectionDisplay = (function() { addHandleStretchTool(handleStretchZCube, "STRETCH_Z", STRETCH_DIRECTION.Z); addHandleScaleTool(handleScaleCube, "SCALE"); - + return that; }()); From b6e4a22db7938359792cd6d8853d1bd5b794213e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 23 Aug 2018 13:06:28 +1200 Subject: [PATCH 084/744] Rotate the mini tablet as it expands --- scripts/system/miniTablet.js | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 17b1587604..ca665b888a 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -79,6 +79,7 @@ { x: 0.5, y: -0.75, z: 0 }, { x: -0.5, y: -0.75, z: 0 } ], + PROXY_EXPAND_DELTA_ROTATION = Quat.fromVec3Degrees({ x: -45, y: 0, z: 0 }), proxyExpandHand, proxyExpandLocalPosition, proxyExpandLocalRotation = Quat.IDENTITY, @@ -88,6 +89,7 @@ proxyExpandStart, proxyInitialWidth, proxyTargetWidth, + proxyTargetLocalRotation, // EventBridge READY_MESSAGE = "ready", // Engine <== Dialog @@ -321,19 +323,28 @@ function sizeUIAboutHandles(scaleFactor) { // Scale UI and move per handles. + var tabletScaleFactor = avatarScale * (1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth); + var dimensions = Vec3.multiply(tabletScaleFactor, PROXY_DIMENSIONS); + var localRotation = Quat.mix(proxyExpandLocalRotation, proxyTargetLocalRotation, scaleFactor); + var localPosition = + Vec3.sum(proxyExpandLocalPosition, + Vec3.multiplyQbyV(proxyExpandLocalRotation, + Vec3.multiply(-tabletScaleFactor, + Vec3.multiplyVbyV(PROXY_EXPAND_HANDLES[proxyExpandHand], PROXY_DIMENSIONS))) + ); + localPosition = Vec3.sum(localPosition, + Vec3.multiplyQbyV(proxyExpandLocalRotation, { x: 0, y: 0.5 * -dimensions.y, z: 0 })); + localPosition = Vec3.sum(localPosition, + Vec3.multiplyQbyV(localRotation, { x: 0, y: 0.5 * dimensions.y, z: 0 })); Overlays.editOverlay(proxyOverlay, { - localPosition: - Vec3.sum(proxyExpandLocalPosition, - Vec3.multiplyQbyV(proxyExpandLocalRotation, - Vec3.multiply(-scaleFactor, - Vec3.multiplyVbyV(PROXY_EXPAND_HANDLES[proxyExpandHand], PROXY_DIMENSIONS))) - ), - dimensions: Vec3.multiply(scaleFactor, PROXY_DIMENSIONS) + localPosition: localPosition, + localRotation: localRotation, + dimensions: dimensions }); Overlays.editOverlay(proxyUIOverlay, { - localPosition: Vec3.multiply(scaleFactor, PROXY_UI_LOCAL_POSITION), - dimensions: Vec3.multiply(scaleFactor, PROXY_UI_DIMENSIONS), - dpi: PROXY_UI_DPI / scaleFactor + localPosition: Vec3.multiply(tabletScaleFactor, PROXY_UI_LOCAL_POSITION), + dimensions: Vec3.multiply(tabletScaleFactor, PROXY_UI_DIMENSIONS), + dpi: PROXY_UI_DPI / tabletScaleFactor }); } @@ -504,9 +515,8 @@ function expandProxy() { var scaleFactor = (Date.now() - proxyExpandStart) / PROXY_EXPAND_DURATION; - var tabletScaleFactor = avatarScale * (1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth); if (scaleFactor < 1) { - sizeUIAboutHandles(tabletScaleFactor); + sizeUIAboutHandles(scaleFactor); proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); return; } @@ -526,6 +536,7 @@ // Start expanding. proxyInitialWidth = PROXY_DIMENSIONS.x; proxyTargetWidth = getTabletWidthFromSettings(); + proxyTargetLocalRotation = Quat.multiply(proxyExpandLocalRotation, PROXY_EXPAND_DELTA_ROTATION); proxyExpandStart = Date.now(); proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); } From 1123ba2b0a5bebd92c87ea336655b9a8472b4b39 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Wed, 22 Aug 2018 22:48:42 -0700 Subject: [PATCH 085/744] exploring optimization in Backend for bffer bindings --- libraries/gpu-gl-common/src/gpu/gl/GLBackend.h | 1 + .../gpu-gl-common/src/gpu/gl/GLBackendInput.cpp | 17 ++++++++++++----- .../src/gpu/gl/GLBackendPipeline.cpp | 2 ++ libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h | 14 +++++++++++++- libraries/gpu-gl/src/gpu/gl41/GL41Backend.h | 1 + .../gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp | 4 ++++ libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 1 + .../gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp | 5 +++++ libraries/gpu/src/gpu/Batch.h | 4 ++-- 9 files changed, 41 insertions(+), 8 deletions(-) diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index cadcec7a56..2fa2df5bfa 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -239,6 +239,7 @@ public: virtual GLuint getFramebufferID(const FramebufferPointer& framebuffer) = 0; virtual GLuint getTextureID(const TexturePointer& texture) final; virtual GLuint getBufferID(const Buffer& buffer) = 0; + virtual GLuint getBufferIDUnsafe(const Buffer& buffer) = 0; virtual GLuint getQueryID(const QueryPointer& query) = 0; virtual GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) = 0; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp index 77e1f90f66..6ce25bc56c 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp @@ -11,6 +11,7 @@ #include "GLBackend.h" #include "GLShared.h" #include "GLInputFormat.h" +#include "GLBuffer.h" using namespace gpu; using namespace gpu::gl; @@ -39,14 +40,20 @@ void GLBackend::do_setInputBuffer(const Batch& batch, size_t paramOffset) { BufferPointer buffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); uint32 channel = batch._params[paramOffset + 3]._uint; - if (channel < getNumInputBuffers()) { + // if (channel < getNumInputBuffers()) { bool isModified = false; if (_input._buffers[channel] != buffer) { _input._buffers[channel] = buffer; GLuint vbo = 0; if (buffer) { - vbo = getBufferID((*buffer)); + // vbo = getBufferID((*buffer)); + // vbo = getBufferIDUnsafe((*buffer)); + auto* object = Backend::getGPUObject((*buffer)); + + if (object) { + vbo = object->_buffer; + } } _input._bufferVBOs[channel] = vbo; @@ -66,7 +73,7 @@ void GLBackend::do_setInputBuffer(const Batch& batch, size_t paramOffset) { if (isModified) { _input._invalidBuffers.set(channel); } - } + // } } void GLBackend::initInput() { @@ -128,7 +135,7 @@ void GLBackend::do_setIndexBuffer(const Batch& batch, size_t paramOffset) { if (indexBuffer != _input._indexBuffer) { _input._indexBuffer = indexBuffer; if (indexBuffer) { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getBufferID(*indexBuffer)); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getBufferIDUnsafe(*indexBuffer)); } else { // FIXME do we really need this? Is there ever a draw call where we care that the element buffer is null? glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -145,7 +152,7 @@ void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) { if (buffer != _input._indirectBuffer) { _input._indirectBuffer = buffer; if (buffer) { - glBindBuffer(GL_DRAW_INDIRECT_BUFFER, getBufferID(*buffer)); + glBindBuffer(GL_DRAW_INDIRECT_BUFFER, getBufferIDUnsafe(*buffer)); } else { // FIXME do we really need this? Is there ever a draw call where we care that the element buffer is null? glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index d3d2bc0938..fd67202863 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -172,6 +172,8 @@ void GLBackend::bindUniformBuffer(uint32_t slot, const BufferPointer& buffer, GL // Sync BufferObject auto* object = syncGPUObject(*bufferState.buffer); + // auto glBO = getBufferIDUnsafe(*buffer); + if (object) { glBindBufferRange(GL_UNIFORM_BUFFER, slot, object->_buffer, bufferState.offset, bufferState.size); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h b/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h index 182014e764..8efbe03b90 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h @@ -49,13 +49,25 @@ public: } } + template + static GLuint getIdUnsafe(GLBackend& backend, const Buffer& buffer) { + GLBufferType* object = Backend::getGPUObject(buffer); + + if (object) { + return object->_buffer; + } + else { + return 0; + } + } + const GLuint& _buffer { _id }; const GLuint _size; const Stamp _stamp; ~GLBuffer(); - virtual void transfer() = 0; + virtual void transfer() {}; protected: GLBuffer(const std::weak_ptr& backend, const Buffer& buffer, GLuint id); diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index c6fbc43ae5..a09b3e9297 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -134,6 +134,7 @@ protected: GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; + GLuint getBufferIDUnsafe(const Buffer& buffer) override; GLuint getResourceBufferID(const Buffer& buffer); GLBuffer* syncGPUObject(const Buffer& buffer) override; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp index 62ade673b4..97ac2739eb 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp @@ -83,6 +83,10 @@ GLuint GL41Backend::getBufferID(const Buffer& buffer) { return GL41Buffer::getId(*this, buffer); } +GLuint GL41Backend::getBufferIDUnsafe(const Buffer& buffer) { + return GL41Backend::getBufferID(buffer); +} + GLuint GL41Backend::getResourceBufferID(const Buffer& buffer) { auto* object = GL41Buffer::sync(*this, buffer); if (object) { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 658bea2a3e..5da4506773 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -235,6 +235,7 @@ protected: GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; + GLuint getBufferIDUnsafe(const Buffer& buffer) override; GLBuffer* syncGPUObject(const Buffer& buffer) override; GLTexture* syncGPUObject(const TexturePointer& texture) override; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp index 2afbea3876..0d26f8f412 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp @@ -51,6 +51,11 @@ GLuint GL45Backend::getBufferID(const Buffer& buffer) { return GL45Buffer::getId(*this, buffer); } +GLuint GL45Backend::getBufferIDUnsafe(const Buffer& buffer) { + //return GL45Buffer::getId(*this, buffer); + return GL45Buffer::getIdUnsafe(*this, buffer); +} + GLBuffer* GL45Backend::syncGPUObject(const Buffer& buffer) { return GL45Buffer::sync(*this, buffer); } diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index bcbfe0616d..2bead507b8 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -417,10 +417,10 @@ public: } const Data& get(uint32 offset) const { - if (offset >= _items.size()) { + /* if (offset >= _items.size()) { static const Data EMPTY; return EMPTY; - } + }*/ return (_items.data() + offset)->_data; } From 473f8d0ca5918bc8b8fc3ff322ec0a527809cc3f Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 23 Aug 2018 11:39:47 -0700 Subject: [PATCH 086/744] Working on the LOD system --- interface/resources/qml/+android/Stats.qml | 15 ++++ interface/resources/qml/Stats.qml | 17 ++++ interface/src/LODManager.cpp | 7 ++ interface/src/LODManager.h | 10 ++- interface/src/ui/Stats.cpp | 6 ++ interface/src/ui/Stats.h | 13 ++- .../render/configSlider/ConfigSlider.qml | 6 +- scripts/developer/utilities/render/lod.js | 88 +++++++++++++++---- scripts/developer/utilities/render/lod.qml | 60 ++++++++++++- 9 files changed, 198 insertions(+), 24 deletions(-) diff --git a/interface/resources/qml/+android/Stats.qml b/interface/resources/qml/+android/Stats.qml index fe827f6ece..0dcb07e730 100644 --- a/interface/resources/qml/+android/Stats.qml +++ b/interface/resources/qml/+android/Stats.qml @@ -264,20 +264,27 @@ Item { StatText { text: "GPU: " + root.gpuFrameTime.toFixed(1) + " ms" } + StatText { + text: "Drawcalls: " + root.drawcalls + } StatText { text: "Triangles: " + root.triangles + " / Material Switches: " + root.materialSwitches } StatText { + visible: root.expanded; text: "GPU Free Memory: " + root.gpuFreeMemory + " MB"; } StatText { + visible: root.expanded; text: "GPU Textures: "; } StatText { + visible: root.expanded; text: " Count: " + root.gpuTextures; } StatText { + visible: root.expanded; text: " Pressure State: " + root.gpuTextureMemoryPressureState; } StatText { @@ -287,27 +294,35 @@ Item { text: " " + root.gpuTextureResourceMemory + " / " + root.gpuTextureResourcePopulatedMemory + " / " + root.texturePendingTransfers + " MB"; } StatText { + visible: root.expanded; text: " Resident Memory: " + root.gpuTextureResidentMemory + " MB"; } StatText { + visible: root.expanded; text: " Framebuffer Memory: " + root.gpuTextureFramebufferMemory + " MB"; } StatText { + visible: root.expanded; text: " External Memory: " + root.gpuTextureExternalMemory + " MB"; } StatText { + visible: root.expanded; text: "GPU Buffers: " } StatText { + visible: root.expanded; text: " Count: " + root.gpuBuffers; } StatText { + visible: root.expanded; text: " Memory: " + root.gpuBufferMemory + " MB"; } StatText { + visible: root.expanded; text: "GL Swapchain Memory: " + root.glContextSwapchainMemory + " MB"; } StatText { + visible: root.expanded; text: "QML Texture Memory: " + root.qmlTextureMemory + " MB"; } StatText { diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 2e6e909312..574ceb62a4 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -285,52 +285,69 @@ Item { StatText { text: "GPU frame size: " + root.gpuFrameSize.x + " x " + root.gpuFrameSize.y } + StatText { + text: "Drawcalls: " + root.drawcalls + } StatText { text: "Triangles: " + root.triangles + " / Material Switches: " + root.materialSwitches } StatText { + visible: root.expanded; text: "GPU Free Memory: " + root.gpuFreeMemory + " MB"; } StatText { + visible: root.expanded; text: "GPU Textures: "; } StatText { + visible: root.expanded; text: " Count: " + root.gpuTextures; } StatText { + visible: root.expanded; text: " Pressure State: " + root.gpuTextureMemoryPressureState; } StatText { + visible: root.expanded; property bool showIdeal: (root.gpuTextureResourceIdealMemory != root.gpuTextureResourceMemory); text: " Resource Allocated " + (showIdeal ? "(Ideal)" : "") + " / Populated / Pending: "; } StatText { + visible: root.expanded; property bool showIdeal: (root.gpuTextureResourceIdealMemory != root.gpuTextureResourceMemory); text: " " + root.gpuTextureResourceMemory + (showIdeal ? ("(" + root.gpuTextureResourceIdealMemory + ")") : "") + " / " + root.gpuTextureResourcePopulatedMemory + " / " + root.texturePendingTransfers + " MB"; } StatText { + visible: root.expanded; text: " Resident Memory: " + root.gpuTextureResidentMemory + " MB"; } StatText { + visible: root.expanded; text: " Framebuffer Memory: " + root.gpuTextureFramebufferMemory + " MB"; } StatText { + visible: root.expanded; text: " External Memory: " + root.gpuTextureExternalMemory + " MB"; } StatText { + visible: root.expanded; text: "GPU Buffers: " } StatText { + visible: root.expanded; text: " Count: " + root.gpuBuffers; } StatText { + visible: root.expanded; text: " Memory: " + root.gpuBufferMemory + " MB"; } StatText { + visible: root.expanded; text: "GL Swapchain Memory: " + root.glContextSwapchainMemory + " MB"; } StatText { + visible: root.expanded; text: "QML Texture Memory: " + root.qmlTextureMemory + " MB"; } StatText { diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index da1f14c450..681bdaf884 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -87,6 +87,7 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; } emit LODDecreased(); + emit LODChanged(); // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime // to provide an FPS just above the decrease threshold. It will drift close to its // true value after a few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary. @@ -108,6 +109,8 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; } emit LODIncreased(); + emit LODChanged(); + // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime // to provide an FPS just below the increase threshold. It will drift close to its // true value after a few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary. @@ -145,6 +148,10 @@ float LODManager::getLODLevel() const { return simpleLOD; } +void LODManager::setLODLevel(float level) { + +} + const float MIN_DECREASE_FPS = 0.5f; void LODManager::setDesktopLODDecreaseFPS(float fps) { diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 8f88da63a8..f0bc1aabf9 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -30,7 +30,9 @@ const float INCREASE_LOD_GAP_FPS = 10.0f; // fps // The default value DEFAULT_OCTREE_SIZE_SCALE means you can be 400 meters away from a 1 meter object in order to see it (which is ~20:20 vision). const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE; // This controls how low the auto-adjust LOD will go. We want a minimum vision of ~20:500 or 0.04 of default -const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.04f; +// const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.04f; +// const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.02f; +const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.01f; class AABox; @@ -60,9 +62,10 @@ class LODManager : public QObject, public Dependency { Q_PROPERTY(float gpuTime READ getGPUTime) Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) Q_PROPERTY(float fps READ getMaxTheoreticalFPS) - Q_PROPERTY(float lodLevel READ getLODLevel) + Q_PROPERTY(float lodLevel READ getLODLevel WRITE setLODLevel NOTIFY LODChanged) Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) + Q_PROPERTY(bool automaticLODAdjust READ getAutomaticLODAdjust WRITE setAutomaticLODAdjust) public: @@ -174,6 +177,7 @@ public: float getAverageRenderTime() const { return _avgRenderTime; }; float getMaxTheoreticalFPS() const { return (float)MSECS_PER_SECOND / _avgRenderTime; }; float getLODLevel() const; + void setLODLevel(float level); signals: @@ -189,6 +193,8 @@ signals: */ void LODDecreased(); + void LODChanged(); + private: LODManager(); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index ce1cd51de1..912e851c00 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -376,6 +376,12 @@ void Stats::updateStats(bool force) { STAT_UPDATE(rectifiedTextureCount, (int)RECTIFIED_TEXTURE_COUNT.load()); STAT_UPDATE(decimatedTextureCount, (int)DECIMATED_TEXTURE_COUNT.load()); + gpu::ContextStats gpuFrameStats; + gpuContext->getFrameStats(gpuFrameStats); + + STAT_UPDATE(drawcalls, gpuFrameStats._DSNumDrawcalls); + + // Incoming packets QLocale locale(QLocale::English); auto voxelPacketsToProcess = qApp->getOctreePacketProcessor().packetsToProcessCount(); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index cf624b54c3..f9043f8f56 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -239,7 +239,8 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, processing, 0) STATS_PROPERTY(int, processingPending, 0) STATS_PROPERTY(int, triangles, 0) - STATS_PROPERTY(int, quads, 0) + STATS_PROPERTY(int, drawcalls, 0) + // STATS_PROPERTY(int, quads, 0) STATS_PROPERTY(int, materialSwitches, 0) STATS_PROPERTY(int, itemConsidered, 0) STATS_PROPERTY(int, itemOutOfView, 0) @@ -708,12 +709,20 @@ signals: */ void trianglesChanged(); + /**jsdoc + * Triggered when the value of the drawcalls property changes. + * This + * @function Stats.drawcallsChanged + * @returns {Signal} + */ + void drawcallsChanged(); + /**jsdoc * Triggered when the value of the quads property changes. * @function Stats.quadsChanged * @returns {Signal} */ - void quadsChanged(); + // void quadsChanged(); /**jsdoc * Triggered when the value of the materialSwitches property changes. diff --git a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml index 41de77fb09..345b29e791 100644 --- a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml +++ b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml @@ -56,13 +56,13 @@ Item { anchors.verticalCenter: root.verticalCenter } - Binding { + /* Binding { id: bindingControl target: root.config property: root.property value: sliderControl.value when: false - } + }*/ HifiControls.Slider { id: sliderControl @@ -73,7 +73,7 @@ Item { anchors.top: root.top anchors.topMargin: 0 - onValueChanged: { root.valueChanged(value) } + // onValueChanged: { root.valueChanged(value) } } HifiControls.Label { diff --git a/scripts/developer/utilities/render/lod.js b/scripts/developer/utilities/render/lod.js index 307e509d39..632f4e50c5 100644 --- a/scripts/developer/utilities/render/lod.js +++ b/scripts/developer/utilities/render/lod.js @@ -16,15 +16,9 @@ var ICON_URL = Script.resolvePath("../../../system/assets/images/lod-i.svg"); var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/lod-a.svg"); - var onScreen = false; + var onTablet = false; // set this to true to use the tablet, false use a floating window - function onClicked() { - if (onScreen) { - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLSource(QMLAPP_URL); - } - } + var onAppScreen = false; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ @@ -35,6 +29,51 @@ var hasEventBridge = false; + var onScreen = false; + var window; + + function onClicked() { + if (onTablet) { + if (onAppScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); + } + } else { + if (onScreen) { + killWindow() + } else { + createWindow() + } + } + } + + function createWindow() { + var qml = Script.resolvePath(QMLAPP_URL); + window = Desktop.createWindow(Script.resolvePath(QMLAPP_URL), { + title: TABLET_BUTTON_NAME, + flags: Desktop.ALWAYS_ON_TOP, + presentationMode: Desktop.PresentationMode.NATIVE, + size: {x: 400, y: 600} + }); + // window.setPosition(200, 50); + window.closed.connect(killWindow); + window.fromQml.connect(fromQml); + onScreen = true + button.editProperties({isActive: true}); + } + + function killWindow() { + if (window !== undefined) { + window.closed.disconnect(killWindow); + window.fromQml.disconnect(fromQml); + window.close() + window = undefined + } + onScreen = false + button.editProperties({isActive: false}) + } + function wireEventBridge(on) { if (!tablet) { print("Warning in wireEventBridge(): 'tablet' undefined!"); @@ -54,23 +93,42 @@ } function onScreenChanged(type, url) { - onScreen = (url === QMLAPP_URL); - button.editProperties({isActive: onScreen}); - wireEventBridge(onScreen); - } - - function fromQml(message) { + if (onTablet) { + if (url === QMLAPP_URL) { + onAppScreen = true; + } else { + onAppScreen = false; + } + + button.editProperties({isActive: onAppScreen}); + wireEventBridge(onAppScreen); + } } button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Script.scriptEnding.connect(function () { - if (onScreen) { + killWindow() + if (onAppScreen) { tablet.gotoHomeScreen(); } button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); tablet.removeButton(button); }); + + function fromQml(message) { + } + + function sendToQml(message) { + if (onTablet) { + tablet.sendToQml(message); + } else { + if (window) { + window.sendToQml(message); + } + } + } + }()); diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index d7b9f1cd57..36a19c10f2 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -10,20 +10,73 @@ // import QtQuick 2.5 import QtQuick.Controls 1.4 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + import "../lib/plotperf" +import "configSlider" Item { id: lodIU anchors.fill:parent + Component.onCompleted: { + Render.getConfig("RenderMainView.DrawSceneOctree").showVisibleCells = false + Render.getConfig("RenderMainView.DrawSceneOctree").showEmptyCells = false + } + + Component.onDestruction: { + Render.getConfig("RenderMainView.DrawSceneOctree").enabled = false + } + + Column { + id: topHeader + spacing: 8 + anchors.right: parent.right + anchors.left: parent.left + + HifiControls.CheckBox { + boxSize: 20 + text: "Show LOD Reticule" + checked: Render.getConfig("RenderMainView.DrawSceneOctree").enabled + onCheckedChanged: { Render.getConfig("RenderMainView.DrawSceneOctree").enabled = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Manual LOD" + checked: LODManager.getAutomaticLODAdjust() + onCheckedChanged: { LODManager.setAutomaticLODAdjust(checked) } + } + ConfigSlider { + showLabel: true + config: LODManager + property: "lodLevel" + max: 13 + min: 0 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } + + } + Column { id: stats spacing: 8 - anchors.fill:parent + anchors.right: parent.right + anchors.left: parent.left + anchors.top: topHeader.bottom + anchors.bottom: parent.bottom function evalEvenHeight() { // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? - return (height - spacing * (children.length - 1)) / children.length + return (height - topLine.height - bottomLine.height - spacing * (children.length - 3)) / (children.length - 2) + } + + Separator { + id: topLine } PlotPerf { @@ -87,6 +140,9 @@ Item { color: "#9999FF" } ] + } + Separator { + id: bottomLine } } } From cee9b909300906f84e156595920e44ab5546dc28 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 23 Aug 2018 16:48:42 -0700 Subject: [PATCH 087/744] working on the LOD behavior --- interface/src/LODManager.cpp | 72 ++++++++++++++++ interface/src/LODManager.h | 11 ++- interface/src/ui/PreferencesDialog.cpp | 9 +- .../render/configSlider/ConfigSlider.qml | 6 +- .../render/configSlider/RichSlider.qml | 82 +++++++++++++++++++ .../utilities/render/configSlider/qmldir | 3 +- scripts/developer/utilities/render/lod.qml | 45 ++++++++-- 7 files changed, 213 insertions(+), 15 deletions(-) create mode 100644 scripts/developer/utilities/render/configSlider/RichSlider.qml diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 681bdaf884..4f90dee2e5 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -136,6 +136,11 @@ void LODManager::resetLODAdjust() { _decreaseFPSExpiry = _increaseFPSExpiry = usecTimestampNow() + LOD_AUTO_ADJUST_PERIOD; } +void LODManager::setAutomaticLODAdjust(bool value) { + _automaticLODAdjust = value; + emit autoLODChanged(); +} + float LODManager::getLODLevel() const { // simpleLOD is a linearized and normalized number that represents how much LOD is being applied. // It ranges from: @@ -149,7 +154,14 @@ float LODManager::getLODLevel() const { } void LODManager::setLODLevel(float level) { + float simpleLOD = level; + if (!_automaticLODAdjust) { + const float LOG_MIN_LOD_RATIO = logf(ADJUST_LOD_MIN_SIZE_SCALE / ADJUST_LOD_MAX_SIZE_SCALE); + float power = LOG_MIN_LOD_RATIO - (simpleLOD * LOG_MIN_LOD_RATIO); + float sizeScale = expf(power) * ADJUST_LOD_MAX_SIZE_SCALE; + setOctreeSizeScale(sizeScale); + } } const float MIN_DECREASE_FPS = 0.5f; @@ -247,3 +259,63 @@ void LODManager::saveSettings() { hmdLODDecreaseFPS.set(getHMDLODDecreaseFPS()); } +void LODManager::setWorldDetailQuality(float quality) { + + static const float MAX_DESKTOP_FPS = 60; + static const float MAX_HMD_FPS = 90; + static const float MIN_FPS = 10; + static const float LOW = 0.25f; + static const float MEDIUM = 0.5f; + static const float HIGH = 0.75f; + static const float THRASHING_DIFFERENCE = 10; + + bool isLowestValue = quality == LOW; + bool isHMDMode = qApp->isHMDMode(); + + float maxFPS = isHMDMode ? MAX_HMD_FPS : MAX_DESKTOP_FPS; + float desiredFPS = maxFPS - THRASHING_DIFFERENCE; + + if (!isLowestValue) { + float calculatedFPS = (maxFPS - (maxFPS * quality)) - THRASHING_DIFFERENCE; + desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS; + } + + if (isHMDMode) { + setHMDLODDecreaseFPS(desiredFPS); + } + else { + setDesktopLODDecreaseFPS(desiredFPS); + } + + emit worldDetailQualityChanged(); +} + +float LODManager::getWorldDetailQuality() const { + + + static const float MAX_DESKTOP_FPS = 60; + static const float MAX_HMD_FPS = 90; + static const float MIN_FPS = 10; + static const float LOW = 0.25f; + static const float MEDIUM = 0.5f; + static const float HIGH = 0.75f; + + bool inHMD = qApp->isHMDMode(); + + float increaseFPS = 0; + if (inHMD) { + increaseFPS = getHMDLODDecreaseFPS(); + } else { + increaseFPS = getDesktopLODDecreaseFPS(); + } + float maxFPS = inHMD ? MAX_HMD_FPS : MAX_DESKTOP_FPS; + float percentage = increaseFPS / maxFPS; + + if (percentage >= HIGH) { + return LOW; + } + else if (percentage >= LOW) { + return MEDIUM; + } + return HIGH; +} diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index f0bc1aabf9..de026274ad 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -65,7 +65,9 @@ class LODManager : public QObject, public Dependency { Q_PROPERTY(float lodLevel READ getLODLevel WRITE setLODLevel NOTIFY LODChanged) Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) - Q_PROPERTY(bool automaticLODAdjust READ getAutomaticLODAdjust WRITE setAutomaticLODAdjust) + Q_PROPERTY(bool automaticLODAdjust READ getAutomaticLODAdjust WRITE setAutomaticLODAdjust NOTIFY autoLODChanged) + + Q_PROPERTY(float worldDetailQuality READ getWorldDetailQuality WRITE setWorldDetailQuality NOTIFY worldDetailQualityChanged) public: @@ -73,7 +75,7 @@ public: * @function LODManager.setAutomaticLODAdjust * @param {boolean} value */ - Q_INVOKABLE void setAutomaticLODAdjust(bool value) { _automaticLODAdjust = value; } + Q_INVOKABLE void setAutomaticLODAdjust(bool value); /**jsdoc * @function LODManager.getAutomaticLODAdjust @@ -179,6 +181,9 @@ public: float getLODLevel() const; void setLODLevel(float level); + void setWorldDetailQuality(float quality); + float getWorldDetailQuality() const; + signals: /**jsdoc @@ -194,6 +199,8 @@ signals: void LODDecreased(); void LODChanged(); + void autoLODChanged(); + void worldDetailQualityChanged(); private: LODManager(); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 79ca2063ec..a271e9436c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -55,7 +55,7 @@ void setupPreferences() { // Graphics quality static const QString GRAPHICS_QUALITY { "Graphics Quality" }; { - static const float MAX_DESKTOP_FPS = 60; + /* static const float MAX_DESKTOP_FPS = 60; static const float MAX_HMD_FPS = 90; static const float MIN_FPS = 10; static const float LOW = 0.25f; @@ -102,6 +102,13 @@ void setupPreferences() { } else { lodManager->setDesktopLODDecreaseFPS(desiredFPS); } + };*/ + auto getter = []()->float { + return DependencyManager::get()->getWorldDetailQuality(); + }; + + auto setter = [](float value) { + DependencyManager::get()->setWorldDetailQuality(value); }; auto wodSlider = new SliderPreference(GRAPHICS_QUALITY, "World Detail", getter, setter); diff --git a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml index 345b29e791..41de77fb09 100644 --- a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml +++ b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml @@ -56,13 +56,13 @@ Item { anchors.verticalCenter: root.verticalCenter } - /* Binding { + Binding { id: bindingControl target: root.config property: root.property value: sliderControl.value when: false - }*/ + } HifiControls.Slider { id: sliderControl @@ -73,7 +73,7 @@ Item { anchors.top: root.top anchors.topMargin: 0 - // onValueChanged: { root.valueChanged(value) } + onValueChanged: { root.valueChanged(value) } } HifiControls.Label { diff --git a/scripts/developer/utilities/render/configSlider/RichSlider.qml b/scripts/developer/utilities/render/configSlider/RichSlider.qml new file mode 100644 index 0000000000..c44727cfa4 --- /dev/null +++ b/scripts/developer/utilities/render/configSlider/RichSlider.qml @@ -0,0 +1,82 @@ +// +// RichSlider.qml +// +// Created by Zach Pomerantz on 2/8/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + + +Item { + HifiConstants { id: hifi } + id: root + + anchors.left: parent.left + anchors.right: parent.right + height: 24 + + function defaultGet() { return 0 } + function defaultSet(value) { } + + property var labelAreaWidthScale: 0.5 + + property bool integral: false + property var valueVarGetter: defaultGet + property var valueVarSetter: defaultSet + // property string property + property alias valueVar : sliderControl.value + // property alias valueSetter : sliderControl.value + property alias min: sliderControl.minimumValue + property alias max: sliderControl.maximumValue + + property alias label: labelControl.text + property bool showLabel: true + + property bool showValue: true + + + + signal valueChanged(real value) + + Component.onCompleted: { + } + + HifiControls.Label { + id: labelControl + text: root.label + enabled: root.showLabel + anchors.left: root.left + width: root.width * root.labelAreaWidthScale + anchors.verticalCenter: root.verticalCenter + } + + HifiControls.Slider { + id: sliderControl + stepSize: root.integral ? 1.0 : 0.0 + anchors.left: labelControl.right + anchors.right: root.right + anchors.rightMargin: 0 + anchors.top: root.top + anchors.topMargin: 0 + onValueChanged: { root.valueVarSetter(value) } + } + + HifiControls.Label { + id: labelValue + enabled: root.showValue + text: sliderControl.value.toFixed(root.integral ? 0 : 2) + anchors.right: labelControl.right + anchors.rightMargin: 5 + anchors.verticalCenter: root.verticalCenter + } + +} diff --git a/scripts/developer/utilities/render/configSlider/qmldir b/scripts/developer/utilities/render/configSlider/qmldir index 6680ec9638..479f786b22 100644 --- a/scripts/developer/utilities/render/configSlider/qmldir +++ b/scripts/developer/utilities/render/configSlider/qmldir @@ -1 +1,2 @@ -ConfigSlider 1.0 ConfigSlider.qml \ No newline at end of file +ConfigSlider 1.0 ConfigSlider.qml +RichSlider 1.0 RichSlider.qml \ No newline at end of file diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 36a19c10f2..25a0d20db6 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -44,21 +44,50 @@ Item { } HifiControls.CheckBox { boxSize: 20 - text: "Manual LOD" - checked: LODManager.getAutomaticLODAdjust() - onCheckedChanged: { LODManager.setAutomaticLODAdjust(checked) } + text: "Auto LOD" + checked: LODManager.automaticLODAdjust + onCheckedChanged: { LODManager.automaticLODAdjust = (checked) } } - ConfigSlider { +property var lodObject: LODManager + + RichSlider { showLabel: true - config: LODManager - property: "lodLevel" - max: 13 - min: 0 + label: "lodLevel" + valueVar: LODManager["lodLevel"] + valueVarSetter: (function (v) { LODManager["lodLevel"] = v }) + max: 1.0 + min: 0.0 integral: false anchors.left: parent.left anchors.right: parent.right } + RichSlider { + showLabel: true + // config: lodObject + // property: "worldDetailQuality" + // valueVar: LODManager["worldDetailQuality"] + label: "World Quality" + valueVar: LODManager["worldDetailQuality"] + // valueVarGetter: { return LODManager["worldDetailQuality"] } + valueVarSetter: (function (v) { LODManager["worldDetailQuality"] = v }) + max: 0.75 + min: 0.25 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } + /* HifiControls.Slider { + id: sliderControl + stepSize: 0.0 + minimumValue: 0.0 + maximumValue: 1.2 + anchors.left: parent.left + anchors.right: parent.right + value: LODManager["lodLevel"] + onValueChanged: { LODManager["lodLevel"] = value } + }*/ } From 57fd824005681efef2b798e4ceb40bc20a67f0c7 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 23 Aug 2018 17:30:23 -0700 Subject: [PATCH 088/744] working on the LOD behavior --- .../render/configSlider/RichSlider.qml | 8 +-- scripts/developer/utilities/render/lod.qml | 59 ++++++++----------- 2 files changed, 27 insertions(+), 40 deletions(-) diff --git a/scripts/developer/utilities/render/configSlider/RichSlider.qml b/scripts/developer/utilities/render/configSlider/RichSlider.qml index c44727cfa4..1613cac1b4 100644 --- a/scripts/developer/utilities/render/configSlider/RichSlider.qml +++ b/scripts/developer/utilities/render/configSlider/RichSlider.qml @@ -28,13 +28,11 @@ Item { function defaultSet(value) { } property var labelAreaWidthScale: 0.5 - property bool integral: false - property var valueVarGetter: defaultGet + property var valueVarSetter: defaultSet - // property string property - property alias valueVar : sliderControl.value - // property alias valueSetter : sliderControl.value + property alias valueVar : sliderControl.value + property alias min: sliderControl.minimumValue property alias max: sliderControl.maximumValue diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 25a0d20db6..452d5c7930 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -42,17 +42,33 @@ Item { checked: Render.getConfig("RenderMainView.DrawSceneOctree").enabled onCheckedChanged: { Render.getConfig("RenderMainView.DrawSceneOctree").enabled = checked } } - HifiControls.CheckBox { - boxSize: 20 - text: "Auto LOD" - checked: LODManager.automaticLODAdjust - onCheckedChanged: { LODManager.automaticLODAdjust = (checked) } - } -property var lodObject: LODManager RichSlider { showLabel: true - label: "lodLevel" + showValue: false + label: "World Quality" + valueVar: LODManager["worldDetailQuality"] + valueVarSetter: (function (v) { LODManager["worldDetailQuality"] = v }) + max: 0.75 + min: 0.25 + integral: false + + anchors.left: autoLOD.left + anchors.right: parent.right + } + + HifiControls.CheckBox { + id: autoLOD + boxSize: 20 + text: "Auto LOD" + checked: LODManager.automaticLODAdjust + onCheckedChanged: { LODManager.automaticLODAdjust = (checked) } + } + + RichSlider { + visible: !LODManager.automaticLODAdjust + showLabel: true + label: "LOD Level" valueVar: LODManager["lodLevel"] valueVarSetter: (function (v) { LODManager["lodLevel"] = v }) max: 1.0 @@ -62,33 +78,6 @@ property var lodObject: LODManager anchors.left: parent.left anchors.right: parent.right } - RichSlider { - showLabel: true - // config: lodObject - // property: "worldDetailQuality" - // valueVar: LODManager["worldDetailQuality"] - label: "World Quality" - valueVar: LODManager["worldDetailQuality"] - // valueVarGetter: { return LODManager["worldDetailQuality"] } - valueVarSetter: (function (v) { LODManager["worldDetailQuality"] = v }) - max: 0.75 - min: 0.25 - integral: false - - anchors.left: parent.left - anchors.right: parent.right - } - /* HifiControls.Slider { - id: sliderControl - stepSize: 0.0 - minimumValue: 0.0 - maximumValue: 1.2 - anchors.left: parent.left - anchors.right: parent.right - value: LODManager["lodLevel"] - onValueChanged: { LODManager["lodLevel"] = value } - }*/ - } Column { From 30c6975198c7e15f55c6b37f1e1975d708d541fe Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 24 Aug 2018 11:53:03 -0700 Subject: [PATCH 089/744] maintain stretch cube position vs mouse when stretching --- .../system/libraries/entitySelectionTool.js | 100 +++++++++++------- 1 file changed, 61 insertions(+), 39 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index f251a1611c..2478160293 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -495,6 +495,8 @@ SelectionDisplay = (function() { var worldRotationX; var worldRotationY; var worldRotationZ; + + var activeStretchCubePanelOffset = null; var previousHandle = null; var previousHandleHelper = null; @@ -1435,34 +1437,7 @@ SelectionDisplay = (function() { Overlays.editOverlay(handleScaleNLEdge, { start: LTNPosition, end: LBNPosition }); Overlays.editOverlay(handleScaleFREdge, { start: RTFPosition, end: RBFPosition }); Overlays.editOverlay(handleScaleFLEdge, { start: LTFPosition, end: LBFPosition }); - - // UPDATE STRETCH CUBES - var stretchCubeDimension = rotateDimension * STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE / - ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var stretchCubeDimensions = { x: stretchCubeDimension, y: stretchCubeDimension, z: stretchCubeDimension }; - var stretchCubeOffset = rotateDimension * STRETCH_CUBE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var stretchXPosition = { x: stretchCubeOffset, y: 0, z: 0 }; - stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); - Overlays.editOverlay(handleStretchXCube, { - position: stretchXPosition, - rotation: rotationX, - dimensions: stretchCubeDimensions - }); - var stretchYPosition = { x: 0, y: stretchCubeOffset, z: 0 }; - stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); - Overlays.editOverlay(handleStretchYCube, { - position: stretchYPosition, - rotation: rotationY, - dimensions: stretchCubeDimensions - }); - var stretchZPosition = { x: 0, y: 0, z: stretchCubeOffset }; - stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); - Overlays.editOverlay(handleStretchZCube, { - position: stretchZPosition, - rotation: rotationZ, - dimensions: stretchCubeDimensions - }); - + // UPDATE STRETCH HIGHLIGHT PANELS var RBFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RBFPosition); var RTFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RTFPosition); @@ -1502,6 +1477,46 @@ SelectionDisplay = (function() { dimensions: stretchPanelZDimensions }); + // UPDATE STRETCH CUBES + var stretchCubeDimension = rotateDimension * STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE / + ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var stretchCubeDimensions = { x: stretchCubeDimension, y: stretchCubeDimension, z: stretchCubeDimension }; + var stretchCubeOffset = rotateDimension * STRETCH_CUBE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var stretchXPosition, stretchYPosition, stretchZPosition; + if (isActiveTool(handleStretchXCube)) { + stretchXPosition = Vec3.subtract(stretchPanelXPosition, activeStretchCubePanelOffset); + } else { + stretchXPosition = { x: stretchCubeOffset, y: 0, z: 0 }; + stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); + } + if (isActiveTool(handleStretchYCube)) { + stretchYPosition = Vec3.subtract(stretchPanelYPosition, activeStretchCubePanelOffset); + } else { + stretchYPosition = { x: 0, y: stretchCubeOffset, z: 0 }; + stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); + } + if (isActiveTool(handleStretchZCube)) { + stretchZPosition = Vec3.subtract(stretchPanelZPosition, activeStretchCubePanelOffset); + } else { + stretchZPosition = { x: 0, y: 0, z: stretchCubeOffset }; + stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); + } + Overlays.editOverlay(handleStretchXCube, { + position: stretchXPosition, + rotation: rotationX, + dimensions: stretchCubeDimensions + }); + Overlays.editOverlay(handleStretchYCube, { + position: stretchYPosition, + rotation: rotationY, + dimensions: stretchCubeDimensions + }); + Overlays.editOverlay(handleStretchZCube, { + position: stretchZPosition, + rotation: rotationZ, + dimensions: stretchCubeDimensions + }); + // UPDATE SELECTION BOX (CURRENTLY INVISIBLE WITH 0 ALPHA FOR TRANSLATE XZ TOOL) var inModeRotate = isActiveTool(handleRotatePitchRing) || isActiveTool(handleRotateYawRing) || @@ -2035,7 +2050,7 @@ SelectionDisplay = (function() { }; // TOOL DEFINITION: HANDLE STRETCH TOOL - function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, scaleHandle) { + function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, cubeHandle) { var directionFor3DStretch = directionVec; var distanceFor3DStretch = 0; var DISTANCE_INFLUENCE_THRESHOLD = 1.2; @@ -2069,7 +2084,9 @@ SelectionDisplay = (function() { var previousPickRay = null; var beginMouseEvent = null; - var onBegin = function(event, pickRay, pickResult) { + var onBegin = function(event, pickRay, pickResult) { + var proportional = directionEnum === STRETCH_DIRECTION.ALL; + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); initialProperties = properties; rotation = (spaceMode === SPACE_LOCAL) ? properties.rotation : Quat.IDENTITY; @@ -2185,10 +2202,7 @@ SelectionDisplay = (function() { if (stretchPanel !== null) { Overlays.editOverlay(stretchPanel, { visible: true }); } - if (scaleHandle !== null) { - Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE_SELECTED }); - } - + var collisionToRemove = "myAvatar"; if (properties.collidesWith.indexOf(collisionToRemove) > -1) { var newCollidesWith = properties.collidesWith.replace(collisionToRemove, ""); @@ -2196,6 +2210,12 @@ SelectionDisplay = (function() { that.replaceCollisionsAfterStretch = true; } + if (!proportional) { + var stretchCubePosition = Overlays.getProperty(cubeHandle, "position"); + var stretchPanelPosition = Overlays.getProperty(stretchPanel, "position"); + activeStretchCubePanelOffset = Vec3.subtract(stretchPanelPosition, stretchCubePosition); + } + previousPickRay = pickRay; beginMouseEvent = event; }; @@ -2204,9 +2224,6 @@ SelectionDisplay = (function() { if (stretchPanel !== null) { Overlays.editOverlay(stretchPanel, { visible: false }); } - if (scaleHandle !== null) { - Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE }); - } if (that.replaceCollisionsAfterStretch) { var newCollidesWith = SelectionManager.savedProperties[SelectionManager.selections[0]].collidesWith; @@ -2214,6 +2231,8 @@ SelectionDisplay = (function() { that.replaceCollisionsAfterStretch = false; } + activeStretchCubePanelOffset = null; + pushCommandForSelections(); }; @@ -2347,19 +2366,22 @@ SelectionDisplay = (function() { } function addHandleStretchTool(overlay, mode, directionEnum) { - var directionVector, offset, stretchPanel; + var directionVector, offset, stretchPanel, handleStretchCube; if (directionEnum === STRETCH_DIRECTION.X) { stretchPanel = handleStretchXPanel; + handleStretchCube = handleStretchXCube; directionVector = { x: -1, y: 0, z: 0 }; } else if (directionEnum === STRETCH_DIRECTION.Y) { stretchPanel = handleStretchYPanel; + handleStretchCube = handleStretchYCube; directionVector = { x: 0, y: -1, z: 0 }; } else if (directionEnum === STRETCH_DIRECTION.Z) { stretchPanel = handleStretchZPanel; + handleStretchCube = handleStretchZCube; directionVector = { x: 0, y: 0, z: -1 }; } offset = Vec3.multiply(directionVector, NEGATE_VECTOR); - var tool = makeStretchTool(mode, directionEnum, directionVector, directionVector, offset, stretchPanel, null); + var tool = makeStretchTool(mode, directionEnum, directionVector, directionVector, offset, stretchPanel, handleStretchCube); return addHandleTool(overlay, tool); } From 3796675123214960ed569f7f83c2788a3005a942 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 24 Aug 2018 15:46:39 -0700 Subject: [PATCH 090/744] script update and visuallyReady entities --- interface/src/Application.cpp | 5 ++- .../src/octree/OctreePacketProcessor.cpp | 4 +++ interface/src/octree/OctreePacketProcessor.h | 1 + interface/src/octree/SafeLanding.cpp | 33 +++++++++++++++---- interface/src/octree/SafeLanding.h | 3 ++ .../src/RenderableModelEntityItem.cpp | 10 +++++- .../src/RenderableModelEntityItem.h | 6 +++- .../src/RenderableZoneEntityItem.cpp | 10 ++++++ libraries/entities/src/EntityItem.h | 3 ++ libraries/entities/src/ModelEntityItem.cpp | 1 + libraries/entities/src/ZoneEntityItem.cpp | 1 + scripts/system/interstitialPage.js | 19 ++++++++--- 12 files changed, 83 insertions(+), 13 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6b2781417d..a4ced55a2d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5519,6 +5519,7 @@ void Application::update(float deltaTime) { return; } + if (!_physicsEnabled) { if (!domainLoadingInProgress) { PROFILE_ASYNC_BEGIN(app, "Scene Loading", ""); @@ -5528,7 +5529,9 @@ void Application::update(float deltaTime) { // we haven't yet enabled physics. we wait until we think we have all the collision information // for nearby entities before starting bullet up. quint64 now = usecTimestampNow(); - if (isServerlessMode() || _octreeProcessor.isLoadSequenceComplete()) { + bool renderReady = _octreeProcessor.isEntitiesRenderReady(); + qDebug() << "--> render ready: " << renderReady; + if (isServerlessMode() || (_octreeProcessor.isLoadSequenceComplete() && renderReady)) { // we've received a new full-scene octree stats packet, or it's been long enough to try again anyway _lastPhysicsCheckTime = now; _fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter; diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index 11f6bbae13..e02c603415 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -138,6 +138,10 @@ bool OctreePacketProcessor::isLoadSequenceComplete() const { return _safeLanding->isLoadSequenceComplete(); } +bool OctreePacketProcessor::isEntitiesRenderReady() const { + return _safeLanding->entitiesRenderReady(); +} + float OctreePacketProcessor::domainLoadProgress() { return _safeLanding->loadingProgressPercentage(); } diff --git a/interface/src/octree/OctreePacketProcessor.h b/interface/src/octree/OctreePacketProcessor.h index fb8f0b581a..8e771de556 100644 --- a/interface/src/octree/OctreePacketProcessor.h +++ b/interface/src/octree/OctreePacketProcessor.h @@ -27,6 +27,7 @@ public: void startEntitySequence(); bool isLoadSequenceComplete() const; + bool isEntitiesRenderReady() const; float domainLoadProgress(); signals: diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 4b8edd5934..bdaaa58fcc 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -36,6 +36,7 @@ void SafeLanding::startEntitySequence(QSharedPointer entityT if (entityTree) { Locker lock(_lock); _entityTree = entityTree; + _trackedEntitiesRenderStatus.clear(); _trackedEntities.clear(); _trackingEntities = true; connect(std::const_pointer_cast(_entityTree).get(), @@ -76,22 +77,24 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { // Only track entities with downloaded collision bodies. _trackedEntities.emplace(entityID, entity); - - float trackedEntityCount = (float)_trackedEntities.size(); - - if (trackedEntityCount > _maxTrackedEntityCount) { - _maxTrackedEntityCount = trackedEntityCount; - } qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); } } } + + _trackedEntitiesRenderStatus.emplace(entityID, entity); + float trackedEntityCount = (float)_trackedEntitiesRenderStatus.size(); + + if (trackedEntityCount > _maxTrackedEntityCount) { + _maxTrackedEntityCount = trackedEntityCount; + } } } void SafeLanding::deleteTrackedEntity(const EntityItemID& entityID) { Locker lock(_lock); _trackedEntities.erase(entityID); + _trackedEntitiesRenderStatus.erase(entityID); } void SafeLanding::setCompletionSequenceNumbers(int first, int last) { @@ -165,6 +168,24 @@ bool SafeLanding::isEntityPhysicsComplete() { return _trackedEntities.empty(); } +bool SafeLanding::entitiesRenderReady() { + Locker lock(_lock); + + for (auto entityMapIter = _trackedEntitiesRenderStatus.begin(); entityMapIter != _trackedEntitiesRenderStatus.end(); ++entityMapIter) { + auto entity = entityMapIter->second; + bool visuallyReady = entity->isVisuallyReady(); + qDebug() << "is entityType: " << EntityTypes::getEntityTypeName(entity->getType()) << " " << visuallyReady; + if (visuallyReady) { + entityMapIter = _trackedEntitiesRenderStatus.erase(entityMapIter); + if (entityMapIter == _trackedEntitiesRenderStatus.end()) { + break; + } + } + } + qDebug() << "list size: -> " << _trackedEntitiesRenderStatus.size(); + return _trackedEntitiesRenderStatus.empty(); +} + float SafeLanding::ElevatedPriority(const EntityItem& entityItem) { return entityItem.getCollisionless() ? 0.0f : 10.0f; } diff --git a/interface/src/octree/SafeLanding.h b/interface/src/octree/SafeLanding.h index 4bede579dc..611b75ab79 100644 --- a/interface/src/octree/SafeLanding.h +++ b/interface/src/octree/SafeLanding.h @@ -18,6 +18,7 @@ #include #include "EntityItem.h" +#include "EntityDynamicInterface.h" class EntityTreeRenderer; class EntityItemID; @@ -29,6 +30,7 @@ public: void setCompletionSequenceNumbers(int first, int last); // 'last' exclusive. void noteReceivedsequenceNumber(int sequenceNumber); bool isLoadSequenceComplete(); + bool entitiesRenderReady(); float loadingProgressPercentage(); private slots: @@ -46,6 +48,7 @@ private: EntityTreePointer _entityTree; using EntityMap = std::map; EntityMap _trackedEntities; + EntityMap _trackedEntitiesRenderStatus; static constexpr int INVALID_SEQUENCE = -1; int _initialStart { INVALID_SEQUENCE }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 34936c2c48..c3cc634236 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1441,7 +1441,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // That is where _currentFrame and _lastAnimated were updated. if (_animating) { DETAILED_PROFILE_RANGE(simulation_physics, "Animate"); - + if (!jointsMapped()) { mapJoints(entity, model->getJointNames()); //else the joint have been mapped before but we have a new animation to load @@ -1457,6 +1457,14 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } } + +void ModelEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { + withWriteLock([&] { + bool visuallyReady = (_prevModelLoaded && _texturesLoaded); + entity->setVisuallyReady(visuallyReady); + }); +} + void ModelEntityRenderer::setIsVisibleInSecondaryCamera(bool value) { Parent::setIsVisibleInSecondaryCamera(value); setKey(_didLastVisualGeometryRequestSucceed); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 45892fdd7f..bcf7296456 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -38,11 +38,13 @@ class ModelEntityWrapper : public ModelEntityItem { using Parent = ModelEntityItem; friend class render::entities::ModelEntityRenderer; +public: + bool isModelLoaded() const; + protected: ModelEntityWrapper(const EntityItemID& entityItemID) : Parent(entityItemID) {} void setModel(const ModelPointer& model); ModelPointer getModel() const; - bool isModelLoaded() const; bool _needsInitialSimulation{ true }; private: @@ -162,6 +164,8 @@ protected: virtual void doRender(RenderArgs* args) override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; + virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; + render::hifi::Tag getTagMask() const override; void setIsVisibleInSecondaryCamera(bool value) override; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index c5035431f6..598b3d1449 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -258,6 +258,16 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen if (hazeChanged) { updateHazeFromEntity(entity); } + + bool visuallyReady = true; + uint32_t skyboxMode = entity->getSkyboxMode(); + if (skyboxMode == COMPONENT_MODE_ENABLED) { + bool skyboxLoadedOrFailed = (_skyboxTexture && (_skyboxTexture->isLoaded() || _skyboxTexture->isFailed())); + qDebug() << "------> " << skyboxLoadedOrFailed; + visuallyReady = (!_skyboxTextureURL.isEmpty() || skyboxLoadedOrFailed); + } + + entity->setVisuallyReady(visuallyReady); } void ZoneEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 47ae8de9ad..490f9b9e6b 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -305,6 +305,7 @@ public: void setDynamic(bool value); virtual bool shouldBePhysical() const { return false; } + bool isVisuallyReady() const { return _visuallyReady; } bool getLocked() const; void setLocked(bool value); @@ -527,6 +528,7 @@ public: void removeCloneID(const QUuid& cloneID); const QVector getCloneIDs() const; void setCloneIDs(const QVector& cloneIDs); + void setVisuallyReady(bool visuallyReady) { _visuallyReady = visuallyReady; } signals: void requestRenderUpdate(); @@ -639,6 +641,7 @@ protected: EntityTreeElementPointer _element; // set by EntityTreeElement void* _physicsInfo { nullptr }; // set by EntitySimulation bool _simulated { false }; // set by EntitySimulation + bool _visuallyReady { true }; bool addActionInternal(EntitySimulationPointer simulation, EntityDynamicPointer action); bool removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation = nullptr); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 5d5344c9c8..774559a628 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -40,6 +40,7 @@ ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem( _type = EntityTypes::Model; _lastKnownCurrentFrame = -1; _color[0] = _color[1] = _color[2] = 0; + _visuallyReady = false; } const QString ModelEntityItem::getTextures() const { diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index f2550e5d3c..58d5ba79c9 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -42,6 +42,7 @@ ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID) : EntityItem(en _shapeType = DEFAULT_SHAPE_TYPE; _compoundShapeURL = DEFAULT_COMPOUND_SHAPE_URL; + _visuallyReady = false; } EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 1507c7bd9a..a5e5e7c0a2 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -32,6 +32,7 @@ var BUTTON_PROPERTIES = { text: "Interstitial" }; + var tablet = null; var button = null; @@ -336,11 +337,12 @@ Overlays.editOverlay(loadingBarPlacard, properties); Overlays.editOverlay(loadingBarProgress, loadingBarProperties); - if (physicsEnabled) { + if (physicsEnabled && !HMD.active) { toolbar.writeProperty("visible", true); - resetValues(); } + resetValues(); + Camera.mode = "first person"; } @@ -356,7 +358,13 @@ Overlays.editOverlay(anchorOverlay, { localPosition: localPosition }); } + + Window.interstitialStatusChanged.connect(function(interstitialMode) { + print("------> insterstitial mode changed " + interstitialMode + " <------"); + }); + function update() { + var downloadInfo = GlobalServices.getDownloadInfo(); var physicsEnabled = Window.isPhysicsEnabled(); var thisInterval = Date.now(); var deltaTime = (thisInterval - lastInterval); @@ -365,7 +373,7 @@ var domainLoadingProgressPercentage = Window.domainLoadingProgress(); var progress = MAX_X_SIZE * domainLoadingProgressPercentage; - print(progress); + //print(progress); if (progress >= target) { target = progress; } @@ -383,6 +391,7 @@ }; Overlays.editOverlay(loadingBarProgress, properties); + print(JSON.stringify(downloadInfo)); if ((physicsEnabled && (currentProgress >= (MAX_X_SIZE - EPSILON)))) { updateOverlays((physicsEnabled || connectionToDomainFailed)); endAudio(); @@ -431,7 +440,9 @@ renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = true; renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = true; renderViewTask.getConfig("LightingModel")["enablePointLight"] = true; - toolbar.writeProperty("visible", true); + if (!HMD.active) { + toolbar.writeProperty("visible", true); + } } Script.scriptEnding.connect(cleanup); From 3167d3c0c726f791fff2d3c3041618e3032cd57a Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 23 Aug 2018 14:05:44 -0700 Subject: [PATCH 091/744] Create TransformNodes for SpatiallyNestables, the mouse, and MyAvatar head --- .../src/avatar/MyAvatarHeadTransformNode.cpp | 23 ++++++++++ .../src/avatar/MyAvatarHeadTransformNode.h | 19 ++++++++ interface/src/raypick/MouseTransformNode.cpp | 43 +++++++++++++++++++ interface/src/raypick/MouseTransformNode.h | 23 ++++++++++ .../shared/src/NestableTransformNode.cpp | 31 +++++++++++++ libraries/shared/src/NestableTransformNode.h | 25 +++++++++++ libraries/shared/src/TransformNode.h | 18 ++++++++ 7 files changed, 182 insertions(+) create mode 100644 interface/src/avatar/MyAvatarHeadTransformNode.cpp create mode 100644 interface/src/avatar/MyAvatarHeadTransformNode.h create mode 100644 interface/src/raypick/MouseTransformNode.cpp create mode 100644 interface/src/raypick/MouseTransformNode.h create mode 100644 libraries/shared/src/NestableTransformNode.cpp create mode 100644 libraries/shared/src/NestableTransformNode.h create mode 100644 libraries/shared/src/TransformNode.h diff --git a/interface/src/avatar/MyAvatarHeadTransformNode.cpp b/interface/src/avatar/MyAvatarHeadTransformNode.cpp new file mode 100644 index 0000000000..82c2f8b703 --- /dev/null +++ b/interface/src/avatar/MyAvatarHeadTransformNode.cpp @@ -0,0 +1,23 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MyAvatarHeadTransformNode.h" + +#include "DependencyManager.h" +#include "AvatarManager.h" +#include "MyAvatar.h" + +Transform MyAvatarHeadTransformNode::getTransform() { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + + glm::vec3 pos = myAvatar->getHeadPosition(); + glm::quat headOri = myAvatar->getHeadOrientation(); + glm::quat ori = headOri * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT); + + return Transform(ori, myAvatar->scaleForChildren(), pos); +} \ No newline at end of file diff --git a/interface/src/avatar/MyAvatarHeadTransformNode.h b/interface/src/avatar/MyAvatarHeadTransformNode.h new file mode 100644 index 0000000000..a7d7144521 --- /dev/null +++ b/interface/src/avatar/MyAvatarHeadTransformNode.h @@ -0,0 +1,19 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_MyAvatarHeadTransformNode_h +#define hifi_MyAvatarHeadTransformNode_h + +#include "TransformNode.h" + +class MyAvatarHeadTransformNode : public TransformNode { +public: + MyAvatarHeadTransformNode() { } + Transform getTransform() override; +}; + +#endif // hifi_MyAvatarHeadTransformNode_h \ No newline at end of file diff --git a/interface/src/raypick/MouseTransformNode.cpp b/interface/src/raypick/MouseTransformNode.cpp new file mode 100644 index 0000000000..4bc85ae69a --- /dev/null +++ b/interface/src/raypick/MouseTransformNode.cpp @@ -0,0 +1,43 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MouseTransformNode.h" + +#include "DependencyManager.h" +#include "PickManager.h" +#include "MouseRayPick.h" + +const PickFilter MOUSE_TRANSFORM_NODE_PICK_FILTER( + 1 << PickFilter::PICK_ENTITIES | + 1 << PickFilter::PICK_AVATARS | + 1 << PickFilter::PICK_INCLUDE_NONCOLLIDABLE +); +const float MOUSE_TRANSFORM_NODE_MAX_DISTANCE = 1000.0f; + +MouseTransformNode::MouseTransformNode() { + _parentMouseRayPick = DependencyManager::get()->addPick(PickQuery::Ray, + std::make_shared(MOUSE_TRANSFORM_NODE_PICK_FILTER, MOUSE_TRANSFORM_NODE_MAX_DISTANCE, true)); +} + +MouseTransformNode::~MouseTransformNode() { + if (DependencyManager::isSet()) { + auto pickManager = DependencyManager::get(); + if (pickManager) { + pickManager->removePick(_parentMouseRayPick); + } + } +} + +Transform MouseTransformNode::getTransform() { + Transform transform; + std::shared_ptr rayPickResult = DependencyManager::get()->getPrevPickResultTyped(_parentMouseRayPick); + if (rayPickResult) { + transform.setTranslation(rayPickResult->intersection); + } + return transform; +} \ No newline at end of file diff --git a/interface/src/raypick/MouseTransformNode.h b/interface/src/raypick/MouseTransformNode.h new file mode 100644 index 0000000000..7a05370dd7 --- /dev/null +++ b/interface/src/raypick/MouseTransformNode.h @@ -0,0 +1,23 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_MouseTransformNode_h +#define hifi_MouseTransformNode_h + +#include "TransformNode.h" + +class MouseTransformNode : public TransformNode { +public: + MouseTransformNode(); + ~MouseTransformNode(); + Transform getTransform() override; + +protected: + unsigned int _parentMouseRayPick = 0; +}; + +#endif // hifi_MouseTransformNode_h \ No newline at end of file diff --git a/libraries/shared/src/NestableTransformNode.cpp b/libraries/shared/src/NestableTransformNode.cpp new file mode 100644 index 0000000000..7fb7187aee --- /dev/null +++ b/libraries/shared/src/NestableTransformNode.cpp @@ -0,0 +1,31 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "NestableTransformNode.h" + +NestableTransformNode::NestableTransformNode(SpatiallyNestableWeakPointer spatiallyNestable, int jointIndex) : + _spatiallyNestable(spatiallyNestable), + _jointIndex(jointIndex) +{ +} + +Transform NestableTransformNode::getTransform() { + auto nestable = _spatiallyNestable.lock(); + if (!nestable) { + return Transform(); + } + + bool success; + Transform jointWorldTransform = nestable->getTransform(_jointIndex, success, 30); + + if (success) { + return jointWorldTransform; + } else { + return Transform(); + } +} \ No newline at end of file diff --git a/libraries/shared/src/NestableTransformNode.h b/libraries/shared/src/NestableTransformNode.h new file mode 100644 index 0000000000..131de9e786 --- /dev/null +++ b/libraries/shared/src/NestableTransformNode.h @@ -0,0 +1,25 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_NestableTransformNode_h +#define hifi_NestableTransformNode_h + +#include "TransformNode.h" + +#include "SpatiallyNestable.h" + +class NestableTransformNode : public TransformNode { +public: + NestableTransformNode(SpatiallyNestableWeakPointer spatiallyNestable, int jointIndex); + Transform getTransform() override; + +protected: + SpatiallyNestableWeakPointer _spatiallyNestable; + int _jointIndex; +}; + +#endif // hifi_NestableTransformNode_h \ No newline at end of file diff --git a/libraries/shared/src/TransformNode.h b/libraries/shared/src/TransformNode.h new file mode 100644 index 0000000000..73223a8cee --- /dev/null +++ b/libraries/shared/src/TransformNode.h @@ -0,0 +1,18 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_TransformNode_h +#define hifi_TransformNode_h + +#include "Transform.h" + +class TransformNode { +public: + virtual Transform getTransform() = 0; +}; + +#endif // hifi_TransformNode_h \ No newline at end of file From b0f8d3e42727e77a79a3e8eee8da507701828d25 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 23 Aug 2018 14:06:39 -0700 Subject: [PATCH 092/744] Add PickTransformNode --- libraries/pointers/src/Pick.h | 1 + libraries/pointers/src/PickManager.cpp | 8 ++++++ libraries/pointers/src/PickManager.h | 2 ++ libraries/pointers/src/PickTransformNode.cpp | 26 ++++++++++++++++++++ libraries/pointers/src/PickTransformNode.h | 22 +++++++++++++++++ 5 files changed, 59 insertions(+) create mode 100644 libraries/pointers/src/PickTransformNode.cpp create mode 100644 libraries/pointers/src/PickTransformNode.h diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index fc09064bd1..1a48aac0ef 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -17,6 +17,7 @@ #include #include +#include enum IntersectionType { NONE = 0, diff --git a/libraries/pointers/src/PickManager.cpp b/libraries/pointers/src/PickManager.cpp index 470d88a46c..b7c57c6aba 100644 --- a/libraries/pointers/src/PickManager.cpp +++ b/libraries/pointers/src/PickManager.cpp @@ -88,6 +88,14 @@ void PickManager::setIncludeItems(unsigned int uid, const QVector& includ } } +Transform PickManager::getResultTransform(unsigned int uid) const { + auto pick = findPick(uid); + if (pick) { + return pick->getResultTransform(); + } + return Transform(); +} + void PickManager::update() { uint64_t expiry = usecTimestampNow() + _perFrameTimeBudget; std::unordered_map>> cachedPicks; diff --git a/libraries/pointers/src/PickManager.h b/libraries/pointers/src/PickManager.h index 242550d837..3209a4c037 100644 --- a/libraries/pointers/src/PickManager.h +++ b/libraries/pointers/src/PickManager.h @@ -40,6 +40,8 @@ public: void setIgnoreItems(unsigned int uid, const QVector& ignore) const; void setIncludeItems(unsigned int uid, const QVector& include) const; + Transform getResultTransform(unsigned int uid) const; + bool isLeftHand(unsigned int uid); bool isRightHand(unsigned int uid); bool isMouse(unsigned int uid); diff --git a/libraries/pointers/src/PickTransformNode.cpp b/libraries/pointers/src/PickTransformNode.cpp new file mode 100644 index 0000000000..fe011b7fcd --- /dev/null +++ b/libraries/pointers/src/PickTransformNode.cpp @@ -0,0 +1,26 @@ +// +// Created by Sabrina Shanman 8/22/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "PickTransformNode.h" + +#include "DependencyManager.h" +#include "PickManager.h" + +PickTransformNode::PickTransformNode(unsigned int uid) : + _uid(uid) +{ +} + +Transform PickTransformNode::getTransform() { + auto pickManager = DependencyManager::get(); + if (!pickManager) { + return Transform(); + } + + return pickManager->getResultTransform(_uid); +} \ No newline at end of file diff --git a/libraries/pointers/src/PickTransformNode.h b/libraries/pointers/src/PickTransformNode.h new file mode 100644 index 0000000000..7547d3f181 --- /dev/null +++ b/libraries/pointers/src/PickTransformNode.h @@ -0,0 +1,22 @@ +// +// Created by Sabrina Shanman 8/22/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_PickTransformNode_h +#define hifi_PickTransformNode_h + +#include "TransformNode.h" + +class PickTransformNode : public TransformNode { +public: + PickTransformNode(unsigned int uid); + Transform getTransform() override; + +protected: + unsigned int _uid; +}; + +#endif // hifi_PickTransformNode_h \ No newline at end of file From 68b86963d4c616413279f21a5ec2adf2845f4580 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 22 Aug 2018 15:27:28 -0700 Subject: [PATCH 093/744] Add CollisionRegion copy constructor --- libraries/shared/src/RegisteredMetaTypes.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index e78dbafd75..3c8ee2ed7d 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -264,6 +264,18 @@ public: class CollisionRegion : public MathPick { public: CollisionRegion() { } + + CollisionRegion(const CollisionRegion& collisionRegion) : + modelURL(collisionRegion.modelURL), + shapeInfo(std::make_shared()), + transform(collisionRegion.transform), + parentID(collisionRegion.parentID), + parentJointIndex(collisionRegion.parentJointIndex), + joint(collisionRegion.joint) + { + shapeInfo->setParams(collisionRegion.shapeInfo->getType(), collisionRegion.shapeInfo->getHalfExtents(), collisionRegion.modelURL.toString()); + } + CollisionRegion(const QVariantMap& pickVariant) { if (pickVariant["shape"].isValid()) { auto shape = pickVariant["shape"].toMap(); From 6e160ad22f790a1b91e19cb1c6d7753c2cb40f48 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 16 Aug 2018 11:32:07 -0700 Subject: [PATCH 094/744] Add TransformNode-based parenting and implement for CollisionPicks --- interface/src/raypick/CollisionPick.cpp | 57 ++++++++++++++++--- interface/src/raypick/CollisionPick.h | 24 ++++---- interface/src/raypick/ParabolaPick.cpp | 12 ++++ interface/src/raypick/ParabolaPick.h | 1 + .../src/raypick/PickScriptingInterface.cpp | 50 +++++++++++++++- .../src/raypick/PickScriptingInterface.h | 3 + interface/src/raypick/RayPick.cpp | 12 ++++ interface/src/raypick/RayPick.h | 1 + interface/src/raypick/StylusPick.cpp | 12 ++++ interface/src/raypick/StylusPick.h | 1 + libraries/pointers/src/Pick.h | 6 +- libraries/shared/src/RegisteredMetaTypes.h | 29 +++++++++- 12 files changed, 184 insertions(+), 24 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 9f2e6da2e8..8c3d28fdfd 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -86,7 +86,7 @@ QVariantMap CollisionPickResult::toVariantMap() const { return variantMap; } -bool CollisionPick::isShapeInfoReady() { +bool CollisionPick::getShapeInfoReady() { if (_mathPick.shouldComputeShapeInfo()) { if (_cachedResource && _cachedResource->isLoaded()) { computeShapeInfo(_mathPick, *_mathPick.shapeInfo, _cachedResource); @@ -95,8 +95,23 @@ bool CollisionPick::isShapeInfoReady() { return false; } + else { + computeShapeInfoDimensionsOnly(_mathPick, *_mathPick.shapeInfo, _cachedResource); + return true; + } +} - return true; +void CollisionPick::computeShapeInfoDimensionsOnly(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { + ShapeType type = shapeInfo.getType(); + glm::vec3 dimensions = pick.transform.getScale(); + QString modelURL = (resource ? resource->getURL().toString() : ""); + if (type == SHAPE_TYPE_COMPOUND) { + shapeInfo.setParams(type, dimensions, modelURL); + } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { + shapeInfo.setParams(type, 0.5f * dimensions, modelURL); + } else { + shapeInfo.setParams(type, 0.5f * dimensions, modelURL); + } } void CollisionPick::computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { @@ -328,8 +343,23 @@ void CollisionPick::computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo } } +CollisionPick::CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, CollisionRegion collisionRegion, PhysicsEnginePointer physicsEngine) : + Pick(filter, maxDistance, enabled), + _mathPick(collisionRegion), + _physicsEngine(physicsEngine) { + if (collisionRegion.shouldComputeShapeInfo()) { + _cachedResource = DependencyManager::get()->getCollisionGeometryResource(collisionRegion.modelURL); + } +} + CollisionRegion CollisionPick::getMathematicalPick() const { - return _mathPick; + if (!parentTransform) { + return _mathPick; + } else { + CollisionRegion transformedMathPick = _mathPick; + transformedMathPick.transform = parentTransform->getTransform().worldTransform(_mathPick.transform); + return transformedMathPick; + } } void CollisionPick::filterIntersections(std::vector& intersections) const { @@ -356,7 +386,7 @@ void CollisionPick::filterIntersections(std::vector& intersec } PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pick) { - if (!isShapeInfoReady()) { + if (!getShapeInfoReady()) { // Cannot compute result return std::make_shared(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); } @@ -367,13 +397,13 @@ PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pi } PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) { - return std::make_shared(pick.toVariantMap(), isShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); + return std::make_shared(pick, getShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); } PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) { - if (!isShapeInfoReady()) { + if (!getShapeInfoReady()) { // Cannot compute result - return std::make_shared(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); + return std::make_shared(pick, CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); } auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *pick.shapeInfo, pick.transform); @@ -382,5 +412,16 @@ PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pi } PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) { - return std::make_shared(pick.toVariantMap(), isShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); + return std::make_shared(pick.toVariantMap(), getShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); +} + +Transform CollisionPick::getResultTransform() const { + PickResultPointer result = getPrevPickResult(); + if (!result) { + return Transform(); + } + + Transform transform; + transform.setTranslation(getMathematicalPick().transform.getTranslation()); + return transform; } \ No newline at end of file diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 6631238737..12d31111d1 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -11,6 +11,7 @@ #include #include #include +#include #include class CollisionPickResult : public PickResult { @@ -24,12 +25,17 @@ public: CollisionPickResult() {} CollisionPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} - CollisionPickResult(const CollisionRegion& searchRegion, LoadState loadState, const std::vector& entityIntersections, const std::vector& avatarIntersections) : + CollisionPickResult(const CollisionRegion& searchRegion, + LoadState loadState, + const std::vector& entityIntersections, + const std::vector& avatarIntersections + ) : PickResult(searchRegion.toVariantMap()), loadState(loadState), intersects(entityIntersections.size() || avatarIntersections.size()), entityIntersections(entityIntersections), - avatarIntersections(avatarIntersections) { + avatarIntersections(avatarIntersections) + { } CollisionPickResult(const CollisionPickResult& collisionPickResult) : PickResult(collisionPickResult.pickVariant) { @@ -54,14 +60,7 @@ public: class CollisionPick : public Pick { public: - CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, CollisionRegion collisionRegion, PhysicsEnginePointer physicsEngine) : - Pick(filter, maxDistance, enabled), - _mathPick(collisionRegion), - _physicsEngine(physicsEngine) { - if (collisionRegion.shouldComputeShapeInfo()) { - _cachedResource = DependencyManager::get()->getCollisionGeometryResource(collisionRegion.modelURL); - } - } + CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, CollisionRegion collisionRegion, PhysicsEnginePointer physicsEngine); CollisionRegion getMathematicalPick() const override; PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { @@ -71,11 +70,12 @@ public: PickResultPointer getOverlayIntersection(const CollisionRegion& pick) override; PickResultPointer getAvatarIntersection(const CollisionRegion& pick) override; PickResultPointer getHUDIntersection(const CollisionRegion& pick) override; - + Transform getResultTransform() const override; protected: // Returns true if pick.shapeInfo is valid. Otherwise, attempts to get the shapeInfo ready for use. - bool isShapeInfoReady(); + bool getShapeInfoReady(); void computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); + void computeShapeInfoDimensionsOnly(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); void filterIntersections(std::vector& intersections) const; CollisionRegion _mathPick; diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp index b3e3f16345..1b37eee096 100644 --- a/interface/src/raypick/ParabolaPick.cpp +++ b/interface/src/raypick/ParabolaPick.cpp @@ -67,4 +67,16 @@ glm::vec3 ParabolaPick::getAcceleration() const { return scale * (DependencyManager::get()->getMyAvatar()->getWorldOrientation() * _accelerationAxis); } return scale * _accelerationAxis; +} + +Transform ParabolaPick::getResultTransform() const { + PickResultPointer result = getPrevPickResult(); + if (!result) { + return Transform(); + } + + auto parabolaResult = std::static_pointer_cast(result); + Transform transform; + transform.setTranslation(parabolaResult->intersection); + return transform; } \ No newline at end of file diff --git a/interface/src/raypick/ParabolaPick.h b/interface/src/raypick/ParabolaPick.h index 99a42a5380..01454390f9 100644 --- a/interface/src/raypick/ParabolaPick.h +++ b/interface/src/raypick/ParabolaPick.h @@ -83,6 +83,7 @@ public: PickResultPointer getOverlayIntersection(const PickParabola& pick) override; PickResultPointer getAvatarIntersection(const PickParabola& pick) override; PickResultPointer getHUDIntersection(const PickParabola& pick) override; + Transform getResultTransform() const override; protected: float _speed; diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 0ed35e5589..f551c0ec90 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -23,6 +23,13 @@ #include "MouseParabolaPick.h" #include "CollisionPick.h" +#include "SpatialParentFinder.h" +#include "NestableTransformNode.h" +#include "PickTransformNode.h" +#include "MouseTransformNode.h" +#include "avatar/MyAvatarHeadTransformNode.h" +#include "avatar/AvatarManager.h" + #include unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, const QVariant& properties) { @@ -276,8 +283,10 @@ unsigned int PickScriptingInterface::createCollisionPick(const QVariant& propert } CollisionRegion collisionRegion(propMap); + auto collisionPick = std::make_shared(filter, maxDistance, enabled, collisionRegion, qApp->getPhysicsEngine()); + collisionPick->parentTransform = createTransformNode(propMap); - return DependencyManager::get()->addPick(PickQuery::Collision, std::make_shared(filter, maxDistance, enabled, collisionRegion, qApp->getPhysicsEngine())); + return DependencyManager::get()->addPick(PickQuery::Collision, collisionPick); } void PickScriptingInterface::enablePick(unsigned int uid) { @@ -351,3 +360,42 @@ unsigned int PickScriptingInterface::getPerFrameTimeBudget() const { void PickScriptingInterface::setPerFrameTimeBudget(unsigned int numUsecs) { DependencyManager::get()->setPerFrameTimeBudget(numUsecs); } + +std::shared_ptr PickScriptingInterface::createTransformNode(const QVariantMap& propMap) { + if (propMap["parentID"].isValid()) { + QUuid parentUuid = propMap["parentID"].toUuid(); + if (!parentUuid.isNull()) { + // Infer object type from parentID + // For now, assume a QUuuid is a SpatiallyNestable. This should change when picks are converted over to QUuids. + bool success; + std::weak_ptr nestablePointer = DependencyManager::get()->find(parentUuid, success, nullptr); + int parentJointIndex = 0; + if (propMap["parentJointIndex"].isValid()) { + parentJointIndex = propMap["parentJointIndex"].toInt(); + } + if (success && !nestablePointer.expired()) { + return std::make_shared(nestablePointer, parentJointIndex); + } + } + + unsigned int pickID = propMap["parentID"].toUInt(); + if (pickID != 0) { + return std::make_shared(pickID); + } + } + + if (propMap["joint"].isValid()) { + QString joint = propMap["joint"].toString(); + if (joint == "Mouse") { + return std::make_shared(); + } else if (joint == "Avatar") { + return std::make_shared(); + } else if (!joint.isNull()) { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + int jointIndex = myAvatar->getJointIndex(joint); + return std::make_shared(myAvatar, jointIndex); + } + } + + return std::shared_ptr(); +} \ No newline at end of file diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 1cad4abf7c..717436a13b 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -320,6 +320,9 @@ public slots: * @returns {number} */ static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; } + +protected: + static std::shared_ptr createTransformNode(const QVariantMap& propMap); }; #endif // hifi_PickScriptingInterface_h diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 96b41dcc72..736d3c1760 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -50,6 +50,18 @@ PickResultPointer RayPick::getHUDIntersection(const PickRay& pick) { return std::make_shared(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), hudRes, pick); } +Transform RayPick::getResultTransform() const { + PickResultPointer result = getPrevPickResult(); + if (!result) { + return Transform(); + } + + auto rayResult = std::static_pointer_cast(result); + Transform transform; + transform.setTranslation(rayResult->intersection); + return transform; +} + glm::vec3 RayPick::intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration) { // TODO: take into account registration glm::vec3 n = rotation * Vectors::FRONT; diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 9410d39c1a..11f985cec2 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -77,6 +77,7 @@ public: PickResultPointer getOverlayIntersection(const PickRay& pick) override; PickResultPointer getAvatarIntersection(const PickRay& pick) override; PickResultPointer getHUDIntersection(const PickRay& pick) override; + Transform getResultTransform() const override; // These are helper functions for projecting and intersecting rays static glm::vec3 intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction); diff --git a/interface/src/raypick/StylusPick.cpp b/interface/src/raypick/StylusPick.cpp index 9021c922a6..69f605e7f9 100644 --- a/interface/src/raypick/StylusPick.cpp +++ b/interface/src/raypick/StylusPick.cpp @@ -225,4 +225,16 @@ PickResultPointer StylusPick::getAvatarIntersection(const StylusTip& pick) { PickResultPointer StylusPick::getHUDIntersection(const StylusTip& pick) { return std::make_shared(pick.toVariantMap()); +} + +Transform StylusPick::getResultTransform() const { + PickResultPointer result = getPrevPickResult(); + if (!result) { + return Transform(); + } + + auto stylusResult = std::static_pointer_cast(result); + Transform transform; + transform.setTranslation(stylusResult->intersection); + return transform; } \ No newline at end of file diff --git a/interface/src/raypick/StylusPick.h b/interface/src/raypick/StylusPick.h index f19e343f8d..ca80e9fbea 100644 --- a/interface/src/raypick/StylusPick.h +++ b/interface/src/raypick/StylusPick.h @@ -66,6 +66,7 @@ public: PickResultPointer getOverlayIntersection(const StylusTip& pick) override; PickResultPointer getAvatarIntersection(const StylusTip& pick) override; PickResultPointer getHUDIntersection(const StylusTip& pick) override; + Transform getResultTransform() const override; bool isLeftHand() const override { return _side == Side::Left; } bool isRightHand() const override { return _side == Side::Right; } diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index 1a48aac0ef..099a791407 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -17,7 +17,7 @@ #include #include -#include +#include enum IntersectionType { NONE = 0, @@ -214,6 +214,10 @@ public: virtual bool isRightHand() const { return false; } virtual bool isMouse() const { return false; } + virtual Transform getResultTransform() const = 0; + + std::shared_ptr parentTransform; + private: PickFilter _filter; const float _maxDistance; diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 3c8ee2ed7d..f1c62a8d16 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -258,8 +258,11 @@ public: * @typedef {object} CollisionPick * @property {Shape} shape - The information about the collision region's size and shape. -* @property {Vec3} position - The position of the collision region. -* @property {Quat} orientation - The orientation of the collision region. +* @property {Vec3} position - The position of the collision region, relative to a parent if defined. +* @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined. +* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay. +* @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) +* @property {string} joint - If "Mouse," parents the pick to the mouse. If "Head," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. */ class CollisionRegion : public MathPick { public: @@ -305,6 +308,15 @@ public: if (pickVariant["orientation"].isValid()) { transform.setRotation(quatFromVariant(pickVariant["orientation"])); } + if (pickVariant["parentID"].isValid()) { + parentID = pickVariant["parentID"].toString(); + } + if (pickVariant["parentJointIndex"].isValid()) { + parentJointIndex = pickVariant["parentJointIndex"].toInt(); + } + if (pickVariant["joint"].isValid()) { + joint = pickVariant["joint"].toString(); + } } QVariantMap toVariantMap() const override { @@ -320,6 +332,14 @@ public: collisionRegion["position"] = vec3toVariant(transform.getTranslation()); collisionRegion["orientation"] = quatToVariant(transform.getRotation()); + if (!parentID.isNull()) { + collisionRegion["parentID"] = parentID; + } + collisionRegion["parentJointIndex"] = parentJointIndex; + if (!joint.isNull()) { + collisionRegion["joint"] = joint; + } + return collisionRegion; } @@ -354,6 +374,11 @@ public: // We can't compute the shapeInfo here without loading the model first, so we delegate that responsibility to the owning CollisionPick std::shared_ptr shapeInfo = std::make_shared(); Transform transform; + + // Parenting information + QUuid parentID; + int parentJointIndex = 0; + QString joint; }; namespace std { From cce62f4d928c703a506f53ca26a62ac85d74620f Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 20 Aug 2018 16:59:03 -0700 Subject: [PATCH 095/744] Do not assume picks with doesIntersect()==false have no results to compare. Fixes CollisionPickResult 'loaded' value always evaluating to false when not colliding --- interface/src/raypick/CollisionPick.h | 1 + libraries/pointers/src/Pick.h | 1 + libraries/pointers/src/PickCacheOptimizer.h | 10 +++++----- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 12d31111d1..7ff3670e53 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -53,6 +53,7 @@ public: QVariantMap toVariantMap() const override; bool doesIntersect() const override { return intersects; } + bool needsToCompareResults() const override { return true; } bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return true; } PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override; diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index 099a791407..9c4de6c46e 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -115,6 +115,7 @@ public: } virtual bool doesIntersect() const = 0; + virtual bool needsToCompareResults() const { return doesIntersect(); } // for example: if we want the closest result, compare based on distance // if we want all results, combine them diff --git a/libraries/pointers/src/PickCacheOptimizer.h b/libraries/pointers/src/PickCacheOptimizer.h index 49a039935c..ae515fc8d7 100644 --- a/libraries/pointers/src/PickCacheOptimizer.h +++ b/libraries/pointers/src/PickCacheOptimizer.h @@ -57,8 +57,8 @@ bool PickCacheOptimizer::checkAndCompareCachedResults(T& pick, PickCache& cac } template -void PickCacheOptimizer::cacheResult(const bool intersects, const PickResultPointer& resTemp, const PickCacheKey& key, PickResultPointer& res, T& mathPick, PickCache& cache, const std::shared_ptr> pick) { - if (intersects) { +void PickCacheOptimizer::cacheResult(const bool needToCompareResults, const PickResultPointer& resTemp, const PickCacheKey& key, PickResultPointer& res, T& mathPick, PickCache& cache, const std::shared_ptr> pick) { + if (needToCompareResults) { cache[mathPick][key] = resTemp; res = res->compareAndProcessNewResult(resTemp); } else { @@ -92,7 +92,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetEntityIntersection(mathematicalPick); if (entityRes) { - cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick); + cacheResult(entityRes->needsToCompareResults(), entityRes, entityKey, res, mathematicalPick, results, pick); } } } @@ -102,7 +102,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetOverlayIntersection(mathematicalPick); if (overlayRes) { - cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick); + cacheResult(overlayRes->needsToCompareResults(), overlayRes, overlayKey, res, mathematicalPick, results, pick); } } } @@ -112,7 +112,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetAvatarIntersection(mathematicalPick); if (avatarRes) { - cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick); + cacheResult(avatarRes->needsToCompareResults(), avatarRes, avatarKey, res, mathematicalPick, results, pick); } } } From 294fe51fdebd5dd5f21f5b6d084d2ec3599f77aa Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Fri, 24 Aug 2018 16:48:23 -0700 Subject: [PATCH 096/744] Trying to simplify the LOD culling test --- interface/src/Application.cpp | 2 +- interface/src/LODManager.cpp | 6 ++++++ interface/src/LODManager.h | 4 ++++ libraries/octree/src/OctreeUtils.cpp | 8 ++++++-- libraries/octree/src/OctreeUtils.h | 1 + libraries/render/src/render/Args.h | 5 +++++ scripts/developer/utilities/render/lod.qml | 16 +++++++++++++++- tests-manual/render-perf/src/main.cpp | 2 +- 8 files changed, 39 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e290531471..817c6b1e43 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5987,7 +5987,7 @@ void Application::updateRenderArgs(float deltaTime) { _viewFrustum.calculate(); } appRenderArgs._renderArgs = RenderArgs(_gpuContext, lodManager->getOctreeSizeScale(), - lodManager->getBoundaryLevelAdjust(), RenderArgs::DEFAULT_RENDER_MODE, + lodManager->getBoundaryLevelAdjust(), lodManager->getSolidAngleHalfTan(), RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); appRenderArgs._renderArgs._scene = getMain3DScene(); diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 4f90dee2e5..c46933b2fc 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -319,3 +319,9 @@ float LODManager::getWorldDetailQuality() const { } return HIGH; } + + +float LODManager::getSolidAngleHalfTan() const { + return getPerspectiveAccuracyAngleTan(_octreeSizeScale, _boundaryLevelAdjust); +} + diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index de026274ad..2ba933f91d 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -69,6 +69,8 @@ class LODManager : public QObject, public Dependency { Q_PROPERTY(float worldDetailQuality READ getWorldDetailQuality WRITE setWorldDetailQuality NOTIFY worldDetailQualityChanged) + Q_PROPERTY(float solidAngleHalfTan READ getSolidAngleHalfTan) + public: /**jsdoc @@ -184,6 +186,8 @@ public: void setWorldDetailQuality(float quality); float getWorldDetailQuality() const; + float getSolidAngleHalfTan() const; + signals: /**jsdoc diff --git a/libraries/octree/src/OctreeUtils.cpp b/libraries/octree/src/OctreeUtils.cpp index 8eaf22e198..be8c194a95 100644 --- a/libraries/octree/src/OctreeUtils.cpp +++ b/libraries/octree/src/OctreeUtils.cpp @@ -64,10 +64,14 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc return voxelSizeScale / powf(2.0f, renderLevel); } -float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) { +float getPerspectiveAccuracyAngleTan(float octreeSizeScale, int boundaryLevelAdjust) { const float maxScale = (float)TREE_SCALE; float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / OCTREE_TO_MESH_RATIO; - return atan(maxScale / visibleDistanceAtMaxScale); + return (maxScale / visibleDistanceAtMaxScale); +} + +float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) { + return atan(getPerspectiveAccuracyAngleTan(octreeSizeScale, boundaryLevelAdjust)); } float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust) { diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index dff56cad64..4539a54dc3 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -29,6 +29,7 @@ float calculateRenderAccuracy(const glm::vec3& position, float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale); +float getPerspectiveAccuracyAngleTan(float octreeSizeScale, int boundaryLevelAdjust); float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust); float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust); diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index 627d4f17b2..a46b914826 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -73,12 +73,14 @@ namespace render { Args(const gpu::ContextPointer& context, float sizeScale = 1.0f, int boundaryLevelAdjust = 0, + float solidAngleHalfTan = 0.1f, RenderMode renderMode = DEFAULT_RENDER_MODE, DisplayMode displayMode = MONO, DebugFlags debugFlags = RENDER_DEBUG_NONE, gpu::Batch* batch = nullptr) : _context(context), _sizeScale(sizeScale), + _solidAngleHalfTan(solidAngleHalfTan), _boundaryLevelAdjust(boundaryLevelAdjust), _renderMode(renderMode), _displayMode(displayMode), @@ -105,8 +107,11 @@ namespace render { std::stack _viewFrustums; glm::ivec4 _viewport { 0.0f, 0.0f, 1.0f, 1.0f }; glm::vec3 _boomOffset { 0.0f, 0.0f, 1.0f }; + float _sizeScale { 1.0f }; int _boundaryLevelAdjust { 0 }; + float _solidAngleHalfTan{ 0.1f }; + RenderMode _renderMode { DEFAULT_RENDER_MODE }; DisplayMode _displayMode { MONO }; DebugFlags _debugFlags { RENDER_DEBUG_NONE }; diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 452d5c7930..688cef142f 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -158,7 +158,21 @@ Item { color: "#9999FF" } ] - } + } + PlotPerf { + title: "Solid Angle Half Tan" + height: parent.evalEvenHeight() + object: LODManager + valueScale: 1.0 + valueUnit: "" + plots: [ + { + prop: "solidAngleHalfTan", + label: "SAHT", + color: "#9999FF" + } + ] + } Separator { id: bottomLine } diff --git a/tests-manual/render-perf/src/main.cpp b/tests-manual/render-perf/src/main.cpp index 9238e0dc1c..c9724ea352 100644 --- a/tests-manual/render-perf/src/main.cpp +++ b/tests-manual/render-perf/src/main.cpp @@ -659,7 +659,7 @@ private: update(); _initContext.makeCurrent(); - RenderArgs renderArgs(_renderThread._gpuContext, DEFAULT_OCTREE_SIZE_SCALE, 0, RenderArgs::DEFAULT_RENDER_MODE, + RenderArgs renderArgs(_renderThread._gpuContext, DEFAULT_OCTREE_SIZE_SCALE, 0, getPerspectiveAccuracyAngleTan(DEFAULT_OCTREE_SIZE_SCALE, 0), RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); QSize windowSize = _size; From bf839ca2912b20f7a080be62c1786c4fda47a90d Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 24 Aug 2018 16:53:38 -0700 Subject: [PATCH 097/744] Handle _unfilteredHandlers first upon processing datagrams --- libraries/networking/src/udt/Socket.cpp | 41 +++++++++++++++---------- libraries/networking/src/udt/Socket.h | 4 +-- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 5b64cc0716..162b045a6e 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -344,7 +344,7 @@ void Socket::readPendingDatagrams() { continue; } - _incomingDatagrams.push({ senderAddress, senderPort, packetSizeWithHeader, + _incomingDatagrams.push_back({ senderAddress, senderPort, packetSizeWithHeader, std::move(buffer), receiveTime }); ++packetsRead; @@ -361,6 +361,28 @@ void Socket::processPendingDatagrams(int) { // setup a HifiSockAddr to read into HifiSockAddr senderSockAddr; + // Process unfiltered packets first. + for (auto datagramIter = _incomingDatagrams.begin(); datagramIter != _incomingDatagrams.end(); ++datagramIter) { + senderSockAddr.setAddress(datagramIter->_senderAddress); + senderSockAddr.setPort(datagramIter->_senderPort); + auto it = _unfilteredHandlers.find(senderSockAddr); + if (it != _unfilteredHandlers.end()) { + // we have a registered unfiltered handler for this HifiSockAddr (eg. STUN packet) - call that and return + if (it->second) { + auto basePacket = BasePacket::fromReceivedPacket(std::move(datagramIter->_datagram), + datagramIter->_datagramLength, + senderSockAddr); + basePacket->setReceiveTime(datagramIter->_receiveTime); + it->second(std::move(basePacket)); + } + + datagramIter = _incomingDatagrams.erase(datagramIter); + if (datagramIter == _incomingDatagrams.end()) { + break; + } + } + } + while (!_incomingDatagrams.empty()) { auto& datagram = _incomingDatagrams.front(); senderSockAddr.setAddress(datagram._senderAddress); @@ -370,19 +392,6 @@ void Socket::processPendingDatagrams(int) { auto it = _unfilteredHandlers.find(senderSockAddr); - if (it != _unfilteredHandlers.end()) { - // we have a registered unfiltered handler for this HifiSockAddr (eg. STUN packet) - call that and return - if (it->second) { - auto basePacket = BasePacket::fromReceivedPacket(std::move(datagram._datagram), datagramSize, - senderSockAddr); - basePacket->setReceiveTime(datagram._receiveTime); - it->second(std::move(basePacket)); - } - - _incomingDatagrams.pop(); - continue; - } - // we're reading a packet so re-start the readyRead backup timer _readyReadBackupTimer->start(); @@ -427,7 +436,7 @@ void Socket::processPendingDatagrams(int) { qCDebug(networking) << "Can't process packet: version" << (unsigned int)NLPacket::versionInHeader(*packet) << ", type" << NLPacket::typeInHeader(*packet); #endif - _incomingDatagrams.pop(); + _incomingDatagrams.pop_front(); continue; } } @@ -444,7 +453,7 @@ void Socket::processPendingDatagrams(int) { } } - _incomingDatagrams.pop(); + _incomingDatagrams.pop_front(); } } diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 078863663f..ef4a457116 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include @@ -156,7 +156,7 @@ private: p_high_resolution_clock::time_point _receiveTime; }; - std::queue _incomingDatagrams; + std::list _incomingDatagrams; int _maxDatagramsRead { 0 }; friend UDTTest; From 92acaade2b736eab9c711d1f3586d90107f097e8 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 24 Aug 2018 17:13:57 -0700 Subject: [PATCH 098/744] trying to fix problems --- interface/src/octree/SafeLanding.cpp | 2 +- .../entities-renderer/src/RenderableModelEntityItem.cpp | 5 +---- libraries/entities-renderer/src/RenderableModelEntityItem.h | 2 -- libraries/entities-renderer/src/RenderableZoneEntityItem.cpp | 4 ++-- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index bdaaa58fcc..262f0b30c2 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -174,7 +174,7 @@ bool SafeLanding::entitiesRenderReady() { for (auto entityMapIter = _trackedEntitiesRenderStatus.begin(); entityMapIter != _trackedEntitiesRenderStatus.end(); ++entityMapIter) { auto entity = entityMapIter->second; bool visuallyReady = entity->isVisuallyReady(); - qDebug() << "is entityType: " << EntityTypes::getEntityTypeName(entity->getType()) << " " << visuallyReady; + qDebug() << "is entityType: " << EntityTypes::getEntityTypeName(entity->getType()) << " " << visuallyReady << " " << entityMapIter->first; if (visuallyReady) { entityMapIter = _trackedEntitiesRenderStatus.erase(entityMapIter); if (entityMapIter == _trackedEntitiesRenderStatus.end()) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c3cc634236..ec46a37a1c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1455,12 +1455,9 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } emit requestRenderUpdate(); } -} - -void ModelEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { withWriteLock([&] { - bool visuallyReady = (_prevModelLoaded && _texturesLoaded); + bool visuallyReady = ((_prevModelLoaded && _texturesLoaded) || model->getURL().isEmpty()); entity->setVisuallyReady(visuallyReady); }); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index bcf7296456..3540fa1127 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -164,8 +164,6 @@ protected: virtual void doRender(RenderArgs* args) override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; - virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; - render::hifi::Tag getTagMask() const override; void setIsVisibleInSecondaryCamera(bool value) override; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index ba561cd8b2..2f36796f84 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -293,8 +293,8 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen uint32_t skyboxMode = entity->getSkyboxMode(); if (skyboxMode == COMPONENT_MODE_ENABLED) { bool skyboxLoadedOrFailed = (_skyboxTexture && (_skyboxTexture->isLoaded() || _skyboxTexture->isFailed())); - qDebug() << "------> " << skyboxLoadedOrFailed; - visuallyReady = (!_skyboxTextureURL.isEmpty() || skyboxLoadedOrFailed); + qDebug() << entity->getEntityItemID() << "------> " << _skyboxTexture->isFailed() << _skyboxTexture->isLoaded() << _skyboxTextureURL.isEmpty(); + visuallyReady = (_skyboxTextureURL.isEmpty() || skyboxLoadedOrFailed); } entity->setVisuallyReady(visuallyReady); From ab810f45050c0e03011cd384070968fe3b1e53ea Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 24 Aug 2018 17:33:06 -0700 Subject: [PATCH 099/744] Try reducing use of shared pointers in O(n2) code --- .../src/avatars/AvatarMixerSlave.cpp | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index f347ff1f10..3e9755ed39 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -210,6 +210,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { } void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { + const Node* nodeRaw = node.data(); auto nodeList = DependencyManager::get(); @@ -220,7 +221,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.nodesBroadcastedTo++; - AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + AvatarMixerClientData* nodeData = reinterpret_cast(nodeRaw->getLinkedData()); nodeData->resetInViewStats(); @@ -260,7 +261,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) bool PALIsOpen = nodeData->getRequestsDomainListData(); // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them - bool getsAnyIgnored = PALIsOpen && node->getCanKick(); + bool getsAnyIgnored = PALIsOpen && nodeRaw->getCanKick(); if (PALIsOpen) { // Increase minimumBytesPerAvatar if the PAL is open @@ -286,8 +287,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes - std::vector avatarsToSort; - std::unordered_map avatarDataToNodes; + std::vector avatarsToSort; + std::unordered_map avatarDataToNodes; std::unordered_map avatarEncodeTimes; std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { // make sure this is an agent that we have avatar data for before considering it for inclusion @@ -295,7 +296,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) && otherNode->getLinkedData()) { const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer(); + AvatarData* otherAvatar = otherNodeData->getAvatarSharedPointer().get(); avatarsToSort.push_back(otherAvatar); avatarDataToNodes[otherAvatar] = otherNode; QUuid id = otherAvatar->getSessionUUID(); @@ -306,7 +307,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) class SortableAvatar: public PrioritySortUtil::Sortable { public: SortableAvatar() = delete; - SortableAvatar(const AvatarSharedPointer& avatar, uint64_t lastEncodeTime) + SortableAvatar(const AvatarData* avatar, uint64_t lastEncodeTime) : _avatar(avatar), _lastEncodeTime(lastEncodeTime) {} glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } float getRadius() const override { @@ -316,10 +317,10 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) uint64_t getTimestamp() const override { return _lastEncodeTime; } - AvatarSharedPointer getAvatar() const { return _avatar; } + const AvatarData* getAvatar() const { return _avatar; } private: - AvatarSharedPointer _avatar; + const AvatarData* _avatar; uint64_t _lastEncodeTime; }; @@ -332,8 +333,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // ignore or sort const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer(); - for (const auto& avatar : avatarsToSort) { - if (avatar == thisAvatar) { + for (const auto avatar : avatarsToSort) { + if (avatar == thisAvatar.get()) { // don't echo updates to self continue; } @@ -356,14 +357,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // and isn't an avatar that the viewing node has ignored // or that has ignored the viewing node if (!avatarNode->getLinkedData() - || avatarNode->getUUID() == node->getUUID() - || (node->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) - || (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { + || avatarNode->getUUID() == nodeRaw->getUUID() + || (nodeRaw->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) + || (avatarNode->isIgnoringNodeWithID(nodeRaw->getUUID()) && !getsAnyIgnored)) { shouldIgnore = true; } else { // Check to see if the space bubble is enabled // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored - if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { + if (nodeRaw->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { float sensorToWorldScale = avatarNodeData->getAvatarSharedPointer()->getSensorToWorldScale(); // Define the scale of the box for the current other node glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f * sensorToWorldScale; @@ -430,7 +431,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); while (!sortedAvatars.empty()) { - const auto avatarData = sortedAvatars.top().getAvatar(); + const AvatarData* avatarData = sortedAvatars.top().getAvatar(); sortedAvatars.pop(); remainingAvatars--; @@ -565,7 +566,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.numBytesSent += numAvatarDataBytes; // send the avatar data PacketList - nodeList->sendPacketList(std::move(avatarPacketList), *node); + nodeList->sendPacketList(std::move(avatarPacketList), *nodeRaw); // record the bytes sent for other avatar data in the AvatarMixerClientData nodeData->recordSentAvatarData(numAvatarDataBytes); @@ -575,7 +576,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (traitsPacketList->getNumPackets() >= 1) { // send the traits packet list - nodeList->sendPacketList(std::move(traitsPacketList), *node); + nodeList->sendPacketList(std::move(traitsPacketList), *nodeRaw); } // record the number of avatars held back this frame From 861d1e26a99be1f9b4839cb25f6ff2fabeac24da Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 24 Aug 2018 18:02:28 -0700 Subject: [PATCH 100/744] Remove unused variable --- libraries/networking/src/udt/Socket.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 162b045a6e..56b7521d7a 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -390,8 +390,6 @@ void Socket::processPendingDatagrams(int) { int datagramSize = datagram._datagramLength; auto receiveTime = datagram._receiveTime; - auto it = _unfilteredHandlers.find(senderSockAddr); - // we're reading a packet so re-start the readyRead backup timer _readyReadBackupTimer->start(); From e4293d9ad57705cfb778737c337ff8018a9a8b13 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Sun, 26 Aug 2018 21:50:01 -0700 Subject: [PATCH 101/744] fixing some entity visual problems --- interface/src/Application.cpp | 1 - interface/src/octree/SafeLanding.cpp | 9 +++++--- .../src/RenderableModelEntityItem.cpp | 21 ++++++++++++------- .../src/RenderableZoneEntityItem.cpp | 7 ++++--- scripts/system/interstitialPage.js | 13 ++++-------- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 092fb51a2a..37e31650a0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5544,7 +5544,6 @@ void Application::update(float deltaTime) { // for nearby entities before starting bullet up. quint64 now = usecTimestampNow(); bool renderReady = _octreeProcessor.isEntitiesRenderReady(); - qDebug() << "--> render ready: " << renderReady; if (isServerlessMode() || (_octreeProcessor.isLoadSequenceComplete() && renderReady)) { // we've received a new full-scene octree stats packet, or it's been long enough to try again anyway _lastPhysicsCheckTime = now; diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 262f0b30c2..8d36b3fcb0 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -13,6 +13,7 @@ #include "EntityTreeRenderer.h" #include "ModelEntityItem.h" #include "InterfaceLogging.h" +#include "Application.h" const int SafeLanding::SEQUENCE_MODULO = std::numeric_limits::max() + 1; @@ -129,7 +130,7 @@ bool SafeLanding::isLoadSequenceComplete() { float SafeLanding::loadingProgressPercentage() { Locker lock(_lock); if (_maxTrackedEntityCount > 0) { - float trackedEntityCount = (float)_trackedEntities.size(); + float trackedEntityCount = (float)_trackedEntitiesRenderStatus.size(); return ((_maxTrackedEntityCount - trackedEntityCount) / _maxTrackedEntityCount); } @@ -170,16 +171,18 @@ bool SafeLanding::isEntityPhysicsComplete() { bool SafeLanding::entitiesRenderReady() { Locker lock(_lock); - + auto entityTree = qApp->getEntities(); for (auto entityMapIter = _trackedEntitiesRenderStatus.begin(); entityMapIter != _trackedEntitiesRenderStatus.end(); ++entityMapIter) { auto entity = entityMapIter->second; bool visuallyReady = entity->isVisuallyReady(); qDebug() << "is entityType: " << EntityTypes::getEntityTypeName(entity->getType()) << " " << visuallyReady << " " << entityMapIter->first; - if (visuallyReady) { + if (visuallyReady || !entityTree->renderableForEntityId(entityMapIter->first)) { entityMapIter = _trackedEntitiesRenderStatus.erase(entityMapIter); if (entityMapIter == _trackedEntitiesRenderStatus.end()) { break; } + } else { + entity->requestRenderUpdate(); } } qDebug() << "list size: -> " << _trackedEntitiesRenderStatus.size(); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index ec46a37a1c..baa20a0583 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1297,9 +1297,21 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } }); - // Check for removal ModelPointer model; withReadLock([&] { model = _model; }); + + withWriteLock([&] { + bool visuallyReady = true; + if (_hasModel) { + if (model && _didLastVisualGeometryRequestSucceed) { + visuallyReady = (_prevModelLoaded && _texturesLoaded); + // qDebug() << visuallyReady; + } + } + entity->setVisuallyReady(visuallyReady); + }); + + // Check for removal if (!_hasModel) { if (model) { model->removeFromScene(scene, transaction); @@ -1445,7 +1457,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce if (!jointsMapped()) { mapJoints(entity, model->getJointNames()); //else the joint have been mapped before but we have a new animation to load - } else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { + } else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { _animation = DependencyManager::get()->getAnimation(entity->getAnimationURL()); _jointMappingCompleted = false; mapJoints(entity, model->getJointNames()); @@ -1455,11 +1467,6 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } emit requestRenderUpdate(); } - - withWriteLock([&] { - bool visuallyReady = ((_prevModelLoaded && _texturesLoaded) || model->getURL().isEmpty()); - entity->setVisuallyReady(visuallyReady); - }); } void ModelEntityRenderer::setIsVisibleInSecondaryCamera(bool value) { diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 2f36796f84..cf452c9cf7 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -291,11 +291,12 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen bool visuallyReady = true; uint32_t skyboxMode = entity->getSkyboxMode(); - if (skyboxMode == COMPONENT_MODE_ENABLED) { + if (skyboxMode == COMPONENT_MODE_ENABLED && !_skyboxTextureURL.isEmpty()) { bool skyboxLoadedOrFailed = (_skyboxTexture && (_skyboxTexture->isLoaded() || _skyboxTexture->isFailed())); - qDebug() << entity->getEntityItemID() << "------> " << _skyboxTexture->isFailed() << _skyboxTexture->isLoaded() << _skyboxTextureURL.isEmpty(); - visuallyReady = (_skyboxTextureURL.isEmpty() || skyboxLoadedOrFailed); + + visuallyReady = skyboxLoadedOrFailed; } + entity->setVisuallyReady(visuallyReady); if (bloomChanged) { diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index a5e5e7c0a2..cf06e222c9 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -223,7 +223,7 @@ function startInterstitialPage() { if (timer === null) { - updateOverlays(Window.isPhysicsEnabled()); + updateOverlays(false); startAudio(); target = 0; currentProgress = 0.1; @@ -364,7 +364,6 @@ }); function update() { - var downloadInfo = GlobalServices.getDownloadInfo(); var physicsEnabled = Window.isPhysicsEnabled(); var thisInterval = Date.now(); var deltaTime = (thisInterval - lastInterval); @@ -373,14 +372,11 @@ var domainLoadingProgressPercentage = Window.domainLoadingProgress(); var progress = MAX_X_SIZE * domainLoadingProgressPercentage; - //print(progress); - if (progress >= target) { + print(progress); + //if (progress >= target) { target = progress; - } + //} - if (physicsEnabled && target < MAX_X_SIZE) { - target = MAX_X_SIZE; - } currentProgress = lerp(currentProgress, target, 0.2); var properties = { localPosition: { x: (1.85 - (currentProgress / 2) - (-0.029 * (currentProgress / MAX_X_SIZE))), y: -0.935, z: 0.0 }, @@ -391,7 +387,6 @@ }; Overlays.editOverlay(loadingBarProgress, properties); - print(JSON.stringify(downloadInfo)); if ((physicsEnabled && (currentProgress >= (MAX_X_SIZE - EPSILON)))) { updateOverlays((physicsEnabled || connectionToDomainFailed)); endAudio(); From d97d655148ab70514241f31c6de19ea691d227c1 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Sun, 26 Aug 2018 23:17:29 -0700 Subject: [PATCH 102/744] USing a simpler halfTan test for culling --- interface/src/LODManager.cpp | 13 +++++++++++-- interface/src/LODManager.h | 2 ++ libraries/octree/src/OctreeUtils.cpp | 6 ------ libraries/octree/src/OctreeUtils.h | 2 -- libraries/render-utils/src/RenderShadowTask.cpp | 4 ++-- libraries/render/src/render/CullTask.cpp | 2 +- libraries/render/src/render/CullTask.h | 6 +++--- scripts/developer/utilities/render/lod.qml | 9 +++++---- 8 files changed, 24 insertions(+), 20 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index c46933b2fc..cd692bccf0 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -237,8 +237,14 @@ QString LODManager::getLODFeedbackText() { bool LODManager::shouldRender(const RenderArgs* args, const AABox& bounds) { // FIXME - eventually we want to use the render accuracy as an indicator for the level of detail // to use in rendering. - float renderAccuracy = calculateRenderAccuracy(args->getViewFrustum().getPosition(), bounds, args->_sizeScale, args->_boundaryLevelAdjust); - return (renderAccuracy > 0.0f); + // float renderAccuracy = calculateRenderAccuracy(args->getViewFrustum().getPosition(), bounds, args->_sizeScale, args->_boundaryLevelAdjust); + // return (renderAccuracy > 0.0f); + + auto pos = args->getViewFrustum().getPosition() - bounds.calcCenter(); + auto dim = bounds.getDimensions(); + auto halfTanSq = 0.25f * glm::dot(dim, dim) / glm::dot(pos, pos); + return (halfTanSq >= args->_solidAngleHalfTan * args->_solidAngleHalfTan); + }; void LODManager::setOctreeSizeScale(float sizeScale) { @@ -325,3 +331,6 @@ float LODManager::getSolidAngleHalfTan() const { return getPerspectiveAccuracyAngleTan(_octreeSizeScale, _boundaryLevelAdjust); } +float LODManager::getSolidAngle() const { + return glm::degrees(2.0 * atan(getSolidAngleHalfTan())); +} diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 2ba933f91d..340292c579 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -70,6 +70,7 @@ class LODManager : public QObject, public Dependency { Q_PROPERTY(float worldDetailQuality READ getWorldDetailQuality WRITE setWorldDetailQuality NOTIFY worldDetailQualityChanged) Q_PROPERTY(float solidAngleHalfTan READ getSolidAngleHalfTan) + Q_PROPERTY(float solidAngle READ getSolidAngle) public: @@ -187,6 +188,7 @@ public: float getWorldDetailQuality() const; float getSolidAngleHalfTan() const; + float getSolidAngle() const; signals: diff --git a/libraries/octree/src/OctreeUtils.cpp b/libraries/octree/src/OctreeUtils.cpp index be8c194a95..7ed9c2ed3c 100644 --- a/libraries/octree/src/OctreeUtils.cpp +++ b/libraries/octree/src/OctreeUtils.cpp @@ -79,9 +79,3 @@ float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust const float smallestSize = 0.01f; return (smallestSize * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT) / boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale); } - -bool isAngularSizeBigEnough(glm::vec3 position, const AACube& cube, float lodScaleFactor, float minDiameter) { - float distance = glm::distance(cube.calcCenter(), position) + MIN_VISIBLE_DISTANCE; - float angularDiameter = cube.getScale() / distance; - return angularDiameter > minDiameter * lodScaleFactor; -} diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index 4539a54dc3..eedbfe8bda 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -39,6 +39,4 @@ const float MIN_ELEMENT_ANGULAR_DIAMETER = 0.0043301f; // radians const float MIN_ENTITY_ANGULAR_DIAMETER = MIN_ELEMENT_ANGULAR_DIAMETER * SQRT_THREE; const float MIN_VISIBLE_DISTANCE = 0.0001f; // helps avoid divide-by-zero check -bool isAngularSizeBigEnough(glm::vec3 position, const AACube& cube, float lodScaleFactor, float minDiameter); - #endif // hifi_OctreeUtils_h diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index b073037e32..137ee07c96 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -62,8 +62,8 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterReceiverFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); - const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterReceiverFilter).asVarying(); - const auto shadowItems = task.addJob("FetchShadowSelection", selectionInputs); + const auto selectionInputs = FilterSpatialSelection::Inputs(shadowSelection, shadowCasterReceiverFilter).asVarying(); + const auto shadowItems = task.addJob("FilterShadowSelection", selectionInputs); // Cull objects that are not visible in camera view. Hopefully the cull functor only performs LOD culling, not // frustum culling or this will make shadow casters out of the camera frustum disappear. diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index b5f1718f6c..7d03f67d3f 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -445,7 +445,7 @@ void ApplyCullFunctorOnItemBounds::run(const RenderContextPointer& renderContext } } -void FetchSpatialSelection::run(const RenderContextPointer& renderContext, +void FilterSpatialSelection::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems) { assert(renderContext->args); auto& scene = renderContext->_scene; diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index 2ef9e92eaa..99ca7abe6c 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -147,12 +147,12 @@ namespace render { }; - class FetchSpatialSelection { + class FilterSpatialSelection { public: using Inputs = render::VaryingSet2; - using JobModel = Job::ModelIO; + using JobModel = Job::ModelIO; - FetchSpatialSelection() {} + FilterSpatialSelection() {} void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); }; diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 688cef142f..c08c089bad 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -160,15 +160,16 @@ Item { ] } PlotPerf { - title: "Solid Angle Half Tan" + title: "Solid Angle" height: parent.evalEvenHeight() object: LODManager valueScale: 1.0 - valueUnit: "" + valueUnit: "deg" + //valueNumDigits: 0 plots: [ { - prop: "solidAngleHalfTan", - label: "SAHT", + prop: "solidAngle", + label: "Solid Angle", color: "#9999FF" } ] From 62814deb328201ba6fe3a122514c9fbc5d61a730 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 27 Aug 2018 08:34:01 -0700 Subject: [PATCH 103/744] Added read of map_d from MTL files --- libraries/fbx/src/OBJReader.cpp | 31 +++++++++++++++++++++---------- libraries/fbx/src/OBJReader.h | 2 ++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index caac08f777..501fb72130 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -263,15 +263,19 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { default: materials[matName] = currentMaterial; #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Last material illumination model:" << currentMaterial.illuminationModel << - " shininess:" << currentMaterial.shininess << " opacity:" << currentMaterial.opacity << - " diffuse color:" << currentMaterial.diffuseColor << " specular color:" << - currentMaterial.specularColor << " emissive color:" << currentMaterial.emissiveColor << - " diffuse texture:" << currentMaterial.diffuseTextureFilename << " specular texture:" << - currentMaterial.specularTextureFilename << " emissive texture:" << - currentMaterial.emissiveTextureFilename << " bump texture:" << - currentMaterial.bumpTextureFilename; - #endif + qCDebug(modelformat) << + "OBJ Reader Last material illumination model:" << currentMaterial.illuminationModel << + " shininess:" << currentMaterial.shininess << + " opacity:" << currentMaterial.opacity << + " diffuse color:" << currentMaterial.diffuseColor << + " specular color:" << currentMaterial.specularColor << + " emissive color:" << currentMaterial.emissiveColor << + " diffuse texture:" << currentMaterial.diffuseTextureFilename << + " specular texture:" << currentMaterial.specularTextureFilename << + " emissive texture:" << currentMaterial.emissiveTextureFilename << + " bump texture:" << currentMaterial.bumpTextureFilename; + " opacity texture:" << currentMaterial.opacityTextureFilename; +#endif return; } QByteArray token = tokenizer.getDatum(); @@ -289,6 +293,8 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.emissiveTextureFilename = ""; currentMaterial.specularTextureFilename = ""; currentMaterial.bumpTextureFilename = ""; + currentMaterial.opacityTextureFilename = ""; + } else if (token == "Ns") { currentMaterial.shininess = tokenizer.getFloat(); } else if (token == "Ni") { @@ -321,7 +327,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.emissiveColor = tokenizer.getVec3(); } else if (token == "Ks") { currentMaterial.specularColor = tokenizer.getVec3(); - } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump")) { + } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump") || (token == "map_d")) { const QByteArray textureLine = tokenizer.getLineAsDatum(); QByteArray filename; OBJMaterialTextureOptions textureOptions; @@ -341,6 +347,8 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } else if ((token == "map_bump") || (token == "bump")) { currentMaterial.bumpTextureFilename = filename; currentMaterial.bumpTextureOptions = textureOptions; + } else if (token == "map_d") { + currentMaterial.opacityTextureFilename = filename; } } } @@ -900,6 +908,9 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m fbxMaterial.normalTexture.isBumpmap = true; fbxMaterial.bumpMultiplier = objMaterial.bumpTextureOptions.bumpMultiplier; } + if (!objMaterial.opacityTextureFilename.isEmpty()) { + fbxMaterial.opacityTexture.filename = objMaterial.opacityTextureFilename; + } modelMaterial->setEmissive(fbxMaterial.emissiveColor); modelMaterial->setAlbedo(fbxMaterial.diffuseColor); diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 13ddc6e21c..e432a3ea51 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -66,6 +66,8 @@ public: QByteArray specularTextureFilename; QByteArray emissiveTextureFilename; QByteArray bumpTextureFilename; + QByteArray opacityTextureFilename; + OBJMaterialTextureOptions bumpTextureOptions; int illuminationModel; bool used { false }; From 51683b0b535f5e987c2a786a325c4e3640d99d41 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 27 Aug 2018 10:16:09 -0700 Subject: [PATCH 104/744] Remove storage of parenting information from CollisionRegion --- libraries/shared/src/RegisteredMetaTypes.h | 27 +--------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index f1c62a8d16..a7f9b72ed2 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -271,10 +271,7 @@ public: CollisionRegion(const CollisionRegion& collisionRegion) : modelURL(collisionRegion.modelURL), shapeInfo(std::make_shared()), - transform(collisionRegion.transform), - parentID(collisionRegion.parentID), - parentJointIndex(collisionRegion.parentJointIndex), - joint(collisionRegion.joint) + transform(collisionRegion.transform) { shapeInfo->setParams(collisionRegion.shapeInfo->getType(), collisionRegion.shapeInfo->getHalfExtents(), collisionRegion.modelURL.toString()); } @@ -308,15 +305,6 @@ public: if (pickVariant["orientation"].isValid()) { transform.setRotation(quatFromVariant(pickVariant["orientation"])); } - if (pickVariant["parentID"].isValid()) { - parentID = pickVariant["parentID"].toString(); - } - if (pickVariant["parentJointIndex"].isValid()) { - parentJointIndex = pickVariant["parentJointIndex"].toInt(); - } - if (pickVariant["joint"].isValid()) { - joint = pickVariant["joint"].toString(); - } } QVariantMap toVariantMap() const override { @@ -332,14 +320,6 @@ public: collisionRegion["position"] = vec3toVariant(transform.getTranslation()); collisionRegion["orientation"] = quatToVariant(transform.getRotation()); - if (!parentID.isNull()) { - collisionRegion["parentID"] = parentID; - } - collisionRegion["parentJointIndex"] = parentJointIndex; - if (!joint.isNull()) { - collisionRegion["joint"] = joint; - } - return collisionRegion; } @@ -374,11 +354,6 @@ public: // We can't compute the shapeInfo here without loading the model first, so we delegate that responsibility to the owning CollisionPick std::shared_ptr shapeInfo = std::make_shared(); Transform transform; - - // Parenting information - QUuid parentID; - int parentJointIndex = 0; - QString joint; }; namespace std { From 402ed4fb76532327066d2ea1448556d68c906e6e Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 27 Aug 2018 10:35:17 -0700 Subject: [PATCH 105/744] More shared pointer tweaks --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 3e9755ed39..367e991a04 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -288,7 +288,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes std::vector avatarsToSort; - std::unordered_map avatarDataToNodes; + std::unordered_map avatarDataToNodes; + std::unordered_map avatarDataToNodesShared; std::unordered_map avatarEncodeTimes; std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { // make sure this is an agent that we have avatar data for before considering it for inclusion @@ -298,7 +299,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarData* otherAvatar = otherNodeData->getAvatarSharedPointer().get(); avatarsToSort.push_back(otherAvatar); - avatarDataToNodes[otherAvatar] = otherNode; + avatarDataToNodes[otherAvatar] = otherNode.data(); + avatarDataToNodesShared[otherAvatar] = otherNode; QUuid id = otherAvatar->getSessionUUID(); avatarEncodeTimes[id] = nodeData->getLastOtherAvatarEncodeTime(id); } @@ -332,9 +334,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarData::_avatarSortCoefficientAge); // ignore or sort - const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer(); + const AvatarData* thisAvatar = nodeData->getAvatarSharedPointer().get(); for (const auto avatar : avatarsToSort) { - if (avatar == thisAvatar.get()) { + if (avatar == thisAvatar) { // don't echo updates to self continue; } @@ -380,7 +382,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // Perform the collision check between the two bounding boxes if (nodeBox.touches(otherNodeBox)) { - nodeData->ignoreOther(node, avatarNode); + nodeData->ignoreOther(node, avatarDataToNodesShared[avatar]); shouldIgnore = !getsAnyIgnored; } } From e2cd695dfb9ada63d8a14664d4e75b13bf2ec978 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 27 Aug 2018 11:01:29 -0700 Subject: [PATCH 106/744] PR code clean up --- interface/src/Application.h | 2 +- interface/src/avatar/MyAvatar.cpp | 1 - interface/src/octree/OctreePacketProcessor.cpp | 2 +- interface/src/octree/OctreePacketProcessor.h | 2 +- interface/src/scripting/WindowScriptingInterface.cpp | 2 +- interface/src/ui/OverlayConductor.cpp | 1 - libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 1 - libraries/entities-renderer/src/RenderableModelEntityItem.h | 4 +--- 8 files changed, 5 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index a6258403a9..f3db4e718d 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -233,7 +233,7 @@ public: bool getPreferAvatarFingerOverStylus() { return false; } void setPreferAvatarFingerOverStylus(bool value); - float getDomainLoadProgress() { return _octreeProcessor.domainLoadProgress(); } + float getDomainLoadingProgress() { return _octreeProcessor.domainLoadingProgress(); } float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); } void setSettingConstrainToolbarPosition(bool setting); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 545ca99109..923d071cc0 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2432,7 +2432,6 @@ void MyAvatar::setHasScriptedBlendshapes(bool hasScriptedBlendshapes) { // send a forced avatarData update to make sure the script can send neutal blendshapes on unload // without having to wait for the update loop, make sure _hasScriptedBlendShapes is still true // before sending the update, or else it won't send the neutal blendshapes to the receiving clients - sendAvatarDataPacket(true); } _hasScriptedBlendShapes = hasScriptedBlendshapes; diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index e02c603415..e670153a77 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -142,6 +142,6 @@ bool OctreePacketProcessor::isEntitiesRenderReady() const { return _safeLanding->entitiesRenderReady(); } -float OctreePacketProcessor::domainLoadProgress() { +float OctreePacketProcessor::domainLoadingProgress() { return _safeLanding->loadingProgressPercentage(); } diff --git a/interface/src/octree/OctreePacketProcessor.h b/interface/src/octree/OctreePacketProcessor.h index 8e771de556..71e22bf240 100644 --- a/interface/src/octree/OctreePacketProcessor.h +++ b/interface/src/octree/OctreePacketProcessor.h @@ -28,7 +28,7 @@ public: void startEntitySequence(); bool isLoadSequenceComplete() const; bool isEntitiesRenderReady() const; - float domainLoadProgress(); + float domainLoadingProgress(); signals: void packetVersionMismatch(); diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index f422a6a8fa..b4c1563795 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -590,5 +590,5 @@ void WindowScriptingInterface::onMessageBoxSelected(int button) { float WindowScriptingInterface::domainLoadingProgress() { - return qApp->getDomainLoadProgress(); + return qApp->getDomainLoadingProgress(); } diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 398f9cf147..e27001567f 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -103,7 +103,6 @@ void OverlayConductor::update(float dt) { bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && !_suppressedByHead; if (targetVisible != currentVisible) { - qDebug() << "setting pinned: " << !targetVisible; offscreenUi->setPinned(!targetVisible); } if (shouldRecenter && !_suppressedByHead) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index baa20a0583..8be5b172ce 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1305,7 +1305,6 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce if (_hasModel) { if (model && _didLastVisualGeometryRequestSucceed) { visuallyReady = (_prevModelLoaded && _texturesLoaded); - // qDebug() << visuallyReady; } } entity->setVisuallyReady(visuallyReady); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 3540fa1127..45892fdd7f 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -38,13 +38,11 @@ class ModelEntityWrapper : public ModelEntityItem { using Parent = ModelEntityItem; friend class render::entities::ModelEntityRenderer; -public: - bool isModelLoaded() const; - protected: ModelEntityWrapper(const EntityItemID& entityItemID) : Parent(entityItemID) {} void setModel(const ModelPointer& model); ModelPointer getModel() const; + bool isModelLoaded() const; bool _needsInitialSimulation{ true }; private: From efa3fa7907a1490f095cc704141fd28139da9708 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 27 Aug 2018 20:42:06 +0200 Subject: [PATCH 107/744] Don't always on top InteractiveWindows (Windows OS) --- interface/resources/qml/InteractiveWindow.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index 800026710d..1c41dd189b 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -146,7 +146,8 @@ Windows.Window { Qt.WindowCloseButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowMinimizeButtonHint; - if ((flags & Desktop.ALWAYS_ON_TOP) === Desktop.ALWAYS_ON_TOP) { + // only use the always on top feature for non Windows OS + if (Qt.platform.os !== "windows" && (flags & Desktop.ALWAYS_ON_TOP) === Desktop.ALWAYS_ON_TOP) { nativeWindowFlags |= Qt.WindowStaysOnTopHint; } nativeWindow.flags = nativeWindowFlags; From 397b03d5d50fec11ed9a84c4e25d63090a317f28 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 27 Aug 2018 14:12:24 -0700 Subject: [PATCH 108/744] Add threshold parameter to collision pick with minimum of 0 --- interface/src/raypick/CollisionPick.cpp | 4 ++-- libraries/physics/src/PhysicsEngine.cpp | 14 ++++++++++---- libraries/physics/src/PhysicsEngine.h | 2 +- libraries/shared/src/RegisteredMetaTypes.h | 19 +++++++++++++++---- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 8c3d28fdfd..407595b86a 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -391,7 +391,7 @@ PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pi return std::make_shared(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); } - auto entityIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_ENTITIES, *pick.shapeInfo, pick.transform); + auto entityIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_ENTITIES, *pick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold); filterIntersections(entityIntersections); return std::make_shared(pick, CollisionPickResult::LOAD_STATE_LOADED, entityIntersections, std::vector()); } @@ -406,7 +406,7 @@ PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pi return std::make_shared(pick, CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); } - auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *pick.shapeInfo, pick.transform); + auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *pick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold); filterIntersections(avatarIntersections); return std::make_shared(pick, CollisionPickResult::LOAD_STATE_LOADED, std::vector(), avatarIntersections); } diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index d5ce60bef9..58c197d6f4 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -898,11 +898,12 @@ void PhysicsEngine::setShowBulletConstraintLimits(bool value) { } struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { - AllContactsCallback(int32_t mask, int32_t group, const ShapeInfo& shapeInfo, const Transform& transform, btCollisionObject* myAvatarCollisionObject) : + AllContactsCallback(int32_t mask, int32_t group, const ShapeInfo& shapeInfo, const Transform& transform, btCollisionObject* myAvatarCollisionObject, float threshold) : btCollisionWorld::ContactResultCallback(), collisionObject(), contacts(), - myAvatarCollisionObject(myAvatarCollisionObject) { + myAvatarCollisionObject(myAvatarCollisionObject), + threshold(threshold) { const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); collisionObject.setCollisionShape(const_cast(collisionShape)); @@ -924,8 +925,13 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { btCollisionObject collisionObject; std::vector contacts; btCollisionObject* myAvatarCollisionObject; + btScalar threshold; btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) override { + if (cp.m_distance1 > -threshold) { + return 0; + } + const btCollisionObject* otherBody; btVector3 penetrationPoint; btVector3 otherPenetrationPoint; @@ -968,14 +974,14 @@ protected: } }; -std::vector PhysicsEngine::contactTest(uint16_t mask, const ShapeInfo& regionShapeInfo, const Transform& regionTransform, uint16_t group) const { +std::vector PhysicsEngine::contactTest(uint16_t mask, const ShapeInfo& regionShapeInfo, const Transform& regionTransform, uint16_t group, float threshold) const { // TODO: Give MyAvatar a motion state so we don't have to do this btCollisionObject* myAvatarCollisionObject = nullptr; if ((mask & USER_COLLISION_GROUP_MY_AVATAR) && _myAvatarController) { myAvatarCollisionObject = _myAvatarController->getCollisionObject(); } - auto contactCallback = AllContactsCallback((int32_t)mask, (int32_t)group, regionShapeInfo, regionTransform, myAvatarCollisionObject); + auto contactCallback = AllContactsCallback((int32_t)mask, (int32_t)group, regionShapeInfo, regionTransform, myAvatarCollisionObject, threshold); _dynamicsWorld->contactTest(&contactCallback.collisionObject, contactCallback); return contactCallback.contacts; diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 317f7ef312..09b68d9a8b 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -142,7 +142,7 @@ public: // Function for getting colliding objects in the world of specified type // See PhysicsCollisionGroups.h for mask flags. - std::vector contactTest(uint16_t mask, const ShapeInfo& regionShapeInfo, const Transform& regionTransform, uint16_t group = USER_COLLISION_GROUP_DYNAMIC) const; + std::vector contactTest(uint16_t mask, const ShapeInfo& regionShapeInfo, const Transform& regionTransform, uint16_t group = USER_COLLISION_GROUP_DYNAMIC, float threshold = 0.0f) const; private: QList removeDynamicsForBody(btRigidBody* body); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index a7f9b72ed2..312e017dc8 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -254,12 +254,13 @@ public: }; /**jsdoc -* A CollisionPick defines a volume for checking collisions in the physics simulation. +* A CollisionRegion defines a volume for checking collisions in the physics simulation. -* @typedef {object} CollisionPick +* @typedef {object} CollisionRegion * @property {Shape} shape - The information about the collision region's size and shape. * @property {Vec3} position - The position of the collision region, relative to a parent if defined. * @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined. +* @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region. * @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay. * @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) * @property {string} joint - If "Mouse," parents the pick to the mouse. If "Head," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. @@ -269,6 +270,7 @@ public: CollisionRegion() { } CollisionRegion(const CollisionRegion& collisionRegion) : + threshold(collisionRegion.threshold), modelURL(collisionRegion.modelURL), shapeInfo(std::make_shared()), transform(collisionRegion.transform) @@ -299,6 +301,10 @@ public: } } + if (pickVariant["threshold"].isValid()) { + threshold = glm::max(0.0f, pickVariant["threshold"].toFloat()); + } + if (pickVariant["position"].isValid()) { transform.setTranslation(vec3FromVariant(pickVariant["position"])); } @@ -317,6 +323,8 @@ public: collisionRegion["shape"] = shape; + collisionRegion["threshold"] = threshold; + collisionRegion["position"] = vec3toVariant(transform.getTranslation()); collisionRegion["orientation"] = quatToVariant(transform.getRotation()); @@ -324,13 +332,15 @@ public: } operator bool() const override { - return !(glm::any(glm::isnan(transform.getTranslation())) || + return !std::isnan(threshold) && + !(glm::any(glm::isnan(transform.getTranslation())) || glm::any(glm::isnan(transform.getRotation())) || shapeInfo->getType() == SHAPE_TYPE_NONE); } bool operator==(const CollisionRegion& other) const { - return glm::all(glm::equal(transform.getTranslation(), other.transform.getTranslation())) && + return threshold == other.threshold && + glm::all(glm::equal(transform.getTranslation(), other.transform.getTranslation())) && glm::all(glm::equal(transform.getRotation(), other.transform.getRotation())) && glm::all(glm::equal(transform.getScale(), other.transform.getScale())) && shapeInfo->getType() == other.shapeInfo->getType() && @@ -354,6 +364,7 @@ public: // We can't compute the shapeInfo here without loading the model first, so we delegate that responsibility to the owning CollisionPick std::shared_ptr shapeInfo = std::make_shared(); Transform transform; + float threshold; }; namespace std { From 44f253c482a9978c378e091d811e8358a785dc88 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 27 Aug 2018 14:54:37 -0700 Subject: [PATCH 109/744] Reduce use of shared pointers and maps --- .../src/avatars/AvatarMixerClientData.cpp | 4 +- .../src/avatars/AvatarMixerSlave.cpp | 60 ++++++++++--------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index f524c071ec..e003fdd769 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -236,9 +236,7 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe } void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other) { - if (isRadiusIgnoring(other)) { - _radiusIgnoredOthers.erase(other); - } + _radiusIgnoredOthers.erase(other); } void AvatarMixerClientData::resetSentTraitData(Node::LocalID nodeLocalID) { diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 367e991a04..2ba54bd6b6 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -287,30 +287,39 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes - std::vector avatarsToSort; - std::unordered_map avatarDataToNodes; - std::unordered_map avatarDataToNodesShared; - std::unordered_map avatarEncodeTimes; + struct AvatarSortData { + SharedNodePointer _nodeShared; + Node* _node; + AvatarData* _avatarData; + quint64 _lastEncodeTime; + }; + std::vector avatarsToSort; + avatarsToSort.reserve(nodeList->size()); + //std::unordered_map avatarDataToNodes; + //std::unordered_map avatarDataToNodesShared; + //std::unordered_map avatarEncodeTimes; std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { + Node* otherNodeRaw = otherNode.data(); // make sure this is an agent that we have avatar data for before considering it for inclusion - if (otherNode->getType() == NodeType::Agent - && otherNode->getLinkedData()) { - const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); + if (otherNodeRaw->getType() == NodeType::Agent + && otherNodeRaw->getLinkedData()) { + const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNodeRaw->getLinkedData()); + AvatarData* otherAvatar = otherNodeData->getAvatarSharedPointer().get(); - avatarsToSort.push_back(otherAvatar); - avatarDataToNodes[otherAvatar] = otherNode.data(); - avatarDataToNodesShared[otherAvatar] = otherNode; - QUuid id = otherAvatar->getSessionUUID(); - avatarEncodeTimes[id] = nodeData->getLastOtherAvatarEncodeTime(id); + //avatarsToSort.push_back(otherAvatar); + //avatarDataToNodes[otherAvatar] = otherNode.data(); + //avatarDataToNodesShared[otherAvatar] = otherNode; + auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(otherAvatar->getSessionUUID()); + avatarsToSort.emplace_back(AvatarSortData({ otherNode, otherNodeRaw, otherAvatar, lastEncodeTime })); } }); class SortableAvatar: public PrioritySortUtil::Sortable { public: SortableAvatar() = delete; - SortableAvatar(const AvatarData* avatar, uint64_t lastEncodeTime) - : _avatar(avatar), _lastEncodeTime(lastEncodeTime) {} + SortableAvatar(const AvatarData* avatar, const Node* avatarNode, uint64_t lastEncodeTime) + : _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {} glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } float getRadius() const override { glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale()); @@ -320,9 +329,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) return _lastEncodeTime; } const AvatarData* getAvatar() const { return _avatar; } + const Node* getNode() const { return _node; } private: const AvatarData* _avatar; + const Node* _node; uint64_t _lastEncodeTime; }; @@ -335,8 +346,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // ignore or sort const AvatarData* thisAvatar = nodeData->getAvatarSharedPointer().get(); - for (const auto avatar : avatarsToSort) { - if (avatar == thisAvatar) { + for (const auto& avatar : avatarsToSort) { + if (avatar._node == nodeRaw) { // don't echo updates to self continue; } @@ -348,7 +359,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // happen if for example the avatar is connected on a desktop and sending // updates at ~30hz. So every 3 frames we skip a frame. - auto avatarNode = avatarDataToNodes[avatar]; + auto avatarNode = avatar._node; assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map const AvatarMixerClientData* avatarNodeData = reinterpret_cast(avatarNode->getLinkedData()); @@ -358,9 +369,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // make sure we have data for this avatar, that it isn't the same node, // and isn't an avatar that the viewing node has ignored // or that has ignored the viewing node - if (!avatarNode->getLinkedData() - || avatarNode->getUUID() == nodeRaw->getUUID() - || (nodeRaw->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) + if ((nodeRaw->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) || (avatarNode->isIgnoringNodeWithID(nodeRaw->getUUID()) && !getsAnyIgnored)) { shouldIgnore = true; } else { @@ -382,7 +391,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // Perform the collision check between the two bounding boxes if (nodeBox.touches(otherNodeBox)) { - nodeData->ignoreOther(node, avatarDataToNodesShared[avatar]); + nodeData->ignoreOther(node, avatar._nodeShared); shouldIgnore = !getsAnyIgnored; } } @@ -419,12 +428,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (!shouldIgnore) { // sort this one for later - uint64_t lastEncodeTime = 0; - std::unordered_map::const_iterator itr = avatarEncodeTimes.find(avatar->getSessionUUID()); - if (itr != avatarEncodeTimes.end()) { - lastEncodeTime = itr->second; - } - sortedAvatars.push(SortableAvatar(avatar, lastEncodeTime)); + sortedAvatars.push(SortableAvatar(avatar._avatarData, avatar._node, avatar._lastEncodeTime)); } } @@ -434,10 +438,10 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); while (!sortedAvatars.empty()) { const AvatarData* avatarData = sortedAvatars.top().getAvatar(); + const Node* otherNode = sortedAvatars.top().getNode(); sortedAvatars.pop(); remainingAvatars--; - auto otherNode = avatarDataToNodes[avatarData]; assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map // NOTE: Here's where we determine if we are over budget and drop to bare minimum data From a76b50ce3308c1d5c5805c4b45f86aace2c83080 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 27 Aug 2018 15:07:19 -0700 Subject: [PATCH 110/744] fix teardown of recording and ScriptEngine(s) --- assignment-client/src/Agent.cpp | 291 ++++++++++-------- .../src/avatars-renderer/Head.cpp | 1 - 2 files changed, 157 insertions(+), 135 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 2f03f15da7..47a64413c0 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -96,7 +96,6 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -177,6 +176,8 @@ void Agent::run() { // Create ScriptEngines on threaded-assignment thread then move to main thread. DependencyManager::set(ScriptEngine::AGENT_SCRIPT)->moveToThread(qApp->thread()); + DependencyManager::set(); + // make sure we request our script once the agent connects to the domain auto nodeList = DependencyManager::get(); @@ -360,154 +361,168 @@ void Agent::scriptRequestFinished() { } void Agent::executeScript() { - _scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload); + // the following block is scoped so that any shared pointers we take here + // are cleared before we call setFinished at the end of the function + { + _scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload); - // setup an Avatar for the script to use - auto scriptedAvatar = DependencyManager::get(); + // setup an Avatar for the script to use + auto scriptedAvatar = DependencyManager::get(); - scriptedAvatar->setID(getSessionUUID()); + scriptedAvatar->setID(getSessionUUID()); - connect(_scriptEngine.data(), SIGNAL(update(float)), - scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection); - scriptedAvatar->setForceFaceTrackerConnected(true); + connect(_scriptEngine.data(), SIGNAL(update(float)), + scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection); + scriptedAvatar->setForceFaceTrackerConnected(true); - // call model URL setters with empty URLs so our avatar, if user, will have the default models - scriptedAvatar->setSkeletonModelURL(QUrl()); + // call model URL setters with empty URLs so our avatar, if user, will have the default models + scriptedAvatar->setSkeletonModelURL(QUrl()); - // force lazy initialization of the head data for the scripted avatar - // since it is referenced below by computeLoudness and getAudioLoudness - scriptedAvatar->getHeadOrientation(); + // force lazy initialization of the head data for the scripted avatar + // since it is referenced below by computeLoudness and getAudioLoudness + scriptedAvatar->getHeadOrientation(); - // give this AvatarData object to the script engine - _scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data()); + // give this AvatarData object to the script engine + _scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data()); - // give scripts access to the Users object - _scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); + // give scripts access to the Users object + _scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); - auto player = DependencyManager::get(); - connect(player.data(), &recording::Deck::playbackStateChanged, [=] { - if (player->isPlaying()) { - auto recordingInterface = DependencyManager::get(); - if (recordingInterface->getPlayFromCurrentLocation()) { - scriptedAvatar->setRecordingBasis(); + auto player = DependencyManager::get(); + connect(player.data(), &recording::Deck::playbackStateChanged, [&player, &scriptedAvatar] { + if (player->isPlaying()) { + auto recordingInterface = DependencyManager::get(); + if (recordingInterface->getPlayFromCurrentLocation()) { + scriptedAvatar->setRecordingBasis(); + } + } else { + scriptedAvatar->clearRecordingBasis(); } - } else { - scriptedAvatar->clearRecordingBasis(); - } - }); + }); - using namespace recording; - static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME); - Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [scriptedAvatar](Frame::ConstPointer frame) { + using namespace recording; + static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME); + Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [scriptedAvatar](Frame::ConstPointer frame) { + + auto recordingInterface = DependencyManager::get(); + bool useFrameSkeleton = recordingInterface->getPlayerUseSkeletonModel(); + + // FIXME - the ability to switch the avatar URL is not actually supported when playing back from a recording + if (!useFrameSkeleton) { + static std::once_flag warning; + std::call_once(warning, [] { + qWarning() << "Recording.setPlayerUseSkeletonModel(false) is not currently supported."; + }); + } + + AvatarData::fromFrame(frame->data, *scriptedAvatar); + }); + + using namespace recording; + static const FrameType AUDIO_FRAME_TYPE = Frame::registerFrameType(AudioConstants::getAudioFrameName()); + Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &scriptedAvatar](Frame::ConstPointer frame) { + static quint16 audioSequenceNumber{ 0 }; + + QByteArray audio(frame->data); + + if (_isNoiseGateEnabled) { + int16_t* samples = reinterpret_cast(audio.data()); + int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; + _audioGate.render(samples, samples, numSamples); + } + + computeLoudness(&audio, scriptedAvatar); + + // state machine to detect gate opening and closing + bool audioGateOpen = (scriptedAvatar->getAudioLoudness() != 0.0f); + bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened + bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed + _audioGateOpen = audioGateOpen; + Q_UNUSED(openedInLastBlock); + + // the codec must be flushed to silence before sending silent packets, + // so delay the transition to silent packets by one packet after becoming silent. + auto packetType = PacketType::MicrophoneAudioNoEcho; + if (!audioGateOpen && !closedInLastBlock) { + packetType = PacketType::SilentAudioFrame; + } + + Transform audioTransform; + auto headOrientation = scriptedAvatar->getHeadOrientation(); + audioTransform.setTranslation(scriptedAvatar->getWorldPosition()); + audioTransform.setRotation(headOrientation); + + QByteArray encodedBuffer; + if (_encoder) { + _encoder->encode(audio, encodedBuffer); + } else { + encodedBuffer = audio; + } + + AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false, + audioTransform, scriptedAvatar->getWorldPosition(), glm::vec3(0), + packetType, _selectedCodecName); + }); + + auto avatarHashMap = DependencyManager::set(); + _scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data()); + + // register ourselves to the script engine + _scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this)); + + _scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); + _scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); + + QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor); + _scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); + + auto entityScriptingInterface = DependencyManager::get(); + + _scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer); + + _scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, + LocationScriptingInterface::locationSetter); auto recordingInterface = DependencyManager::get(); - bool useFrameSkeleton = recordingInterface->getPlayerUseSkeletonModel(); + _scriptEngine->registerGlobalObject("Recording", recordingInterface.data()); - // FIXME - the ability to switch the avatar URL is not actually supported when playing back from a recording - if (!useFrameSkeleton) { - static std::once_flag warning; - std::call_once(warning, [] { - qWarning() << "Recording.setPlayerUseSkeletonModel(false) is not currently supported."; - }); + entityScriptingInterface->init(); + + _entityViewer.init(); + + entityScriptingInterface->setEntityTree(_entityViewer.getTree()); + + DependencyManager::set(_entityViewer.getTree()); + + _avatarAudioTimer.start(); + + // Agents should run at 45hz + static const int AVATAR_DATA_HZ = 45; + static const int AVATAR_DATA_IN_MSECS = MSECS_PER_SECOND / AVATAR_DATA_HZ; + QTimer* avatarDataTimer = new QTimer(this); + connect(avatarDataTimer, &QTimer::timeout, this, &Agent::processAgentAvatar); + avatarDataTimer->setSingleShot(false); + avatarDataTimer->setInterval(AVATAR_DATA_IN_MSECS); + avatarDataTimer->setTimerType(Qt::PreciseTimer); + avatarDataTimer->start(); + + _scriptEngine->run(); + + Frame::clearFrameHandler(AUDIO_FRAME_TYPE); + Frame::clearFrameHandler(AVATAR_FRAME_TYPE); + + if (recordingInterface->isPlaying()) { + recordingInterface->stopPlaying(); } - AvatarData::fromFrame(frame->data, *scriptedAvatar); - }); - - using namespace recording; - static const FrameType AUDIO_FRAME_TYPE = Frame::registerFrameType(AudioConstants::getAudioFrameName()); - Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &scriptedAvatar](Frame::ConstPointer frame) { - static quint16 audioSequenceNumber{ 0 }; - - QByteArray audio(frame->data); - - if (_isNoiseGateEnabled) { - int16_t* samples = reinterpret_cast(audio.data()); - int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; - _audioGate.render(samples, samples, numSamples); + if (recordingInterface->isRecording()) { + recordingInterface->stopRecording(); } - computeLoudness(&audio, scriptedAvatar); + avatarDataTimer->stop(); + _avatarAudioTimer.stop(); + } - // state machine to detect gate opening and closing - bool audioGateOpen = (scriptedAvatar->getAudioLoudness() != 0.0f); - bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened - bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed - _audioGateOpen = audioGateOpen; - Q_UNUSED(openedInLastBlock); - - // the codec must be flushed to silence before sending silent packets, - // so delay the transition to silent packets by one packet after becoming silent. - auto packetType = PacketType::MicrophoneAudioNoEcho; - if (!audioGateOpen && !closedInLastBlock) { - packetType = PacketType::SilentAudioFrame; - } - - Transform audioTransform; - auto headOrientation = scriptedAvatar->getHeadOrientation(); - audioTransform.setTranslation(scriptedAvatar->getWorldPosition()); - audioTransform.setRotation(headOrientation); - - QByteArray encodedBuffer; - if (_encoder) { - _encoder->encode(audio, encodedBuffer); - } else { - encodedBuffer = audio; - } - - AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false, - audioTransform, scriptedAvatar->getWorldPosition(), glm::vec3(0), - packetType, _selectedCodecName); - }); - - auto avatarHashMap = DependencyManager::set(); - _scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data()); - - // register ourselves to the script engine - _scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this)); - - _scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); - _scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); - - QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor); - _scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); - - auto entityScriptingInterface = DependencyManager::get(); - - _scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer); - - _scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, - LocationScriptingInterface::locationSetter); - - auto recordingInterface = DependencyManager::get(); - _scriptEngine->registerGlobalObject("Recording", recordingInterface.data()); - - entityScriptingInterface->init(); - - _entityViewer.init(); - - entityScriptingInterface->setEntityTree(_entityViewer.getTree()); - - DependencyManager::set(_entityViewer.getTree()); - - QMetaObject::invokeMethod(&_avatarAudioTimer, "start"); - - // Agents should run at 45hz - static const int AVATAR_DATA_HZ = 45; - static const int AVATAR_DATA_IN_MSECS = MSECS_PER_SECOND / AVATAR_DATA_HZ; - QTimer* avatarDataTimer = new QTimer(this); - connect(avatarDataTimer, &QTimer::timeout, this, &Agent::processAgentAvatar); - avatarDataTimer->setSingleShot(false); - avatarDataTimer->setInterval(AVATAR_DATA_IN_MSECS); - avatarDataTimer->setTimerType(Qt::PreciseTimer); - avatarDataTimer->start(); - - _scriptEngine->run(); - - Frame::clearFrameHandler(AUDIO_FRAME_TYPE); - Frame::clearFrameHandler(AVATAR_FRAME_TYPE); - - DependencyManager::destroy(); setFinished(true); } @@ -859,17 +874,25 @@ void Agent::aboutToFinish() { DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - DependencyManager::destroy(); + + // drop our shared pointer to the script engine, then ask ScriptEngines to shutdown scripting + // this ensures that the ScriptEngine goes down before ScriptEngines + _scriptEngine.clear(); + + { + DependencyManager::get()->shutdownScripting(); + } + + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); - // cleanup codec & encoder if (_codec && _encoder) { _codec->releaseEncoder(_encoder); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index bdee6d9147..9cf034863e 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include From c29c9f98c1d46a3665aa5dd3ba3b097333cc9d6b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Aug 2018 11:15:57 +1200 Subject: [PATCH 111/744] Make Cancel in HMD settings dialogs return to Settings dialog --- .../hifi/tablet/tabletWindows/TabletPreferencesDialog.qml | 6 +++++- interface/src/Menu.cpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml index 05f45dc61e..31d246be2d 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -50,7 +50,11 @@ Item { section.restoreAll(); } - closeDialog(); + if (HMD.active) { + tablet.popFromStack(); + } else { + closeDialog(); + } } function closeDialog() { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a7d7dcf8b3..72191de994 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -254,7 +254,7 @@ Menu::Menu() { connect(action, &QAction::triggered, [] { auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); auto hmd = DependencyManager::get(); - tablet->loadQMLSource("hifi/tablet/ControllerSettings.qml"); + tablet->pushOntoStack("hifi/tablet/ControllerSettings.qml"); if (!hmd->getShouldShowTablet()) { hmd->toggleShouldShowTablet(); From c6e6da2e3f91b0eb0e7db056e1e762c737968a02 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 27 Aug 2018 15:38:36 -0700 Subject: [PATCH 112/744] Make MouseTransformNode actually parent the pick to a mouse rather than a MouseRayPick, and properly include rotation information --- interface/src/raypick/MouseTransformNode.cpp | 42 ++++++-------------- interface/src/raypick/MouseTransformNode.h | 5 --- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/interface/src/raypick/MouseTransformNode.cpp b/interface/src/raypick/MouseTransformNode.cpp index 4bc85ae69a..9caa4865a2 100644 --- a/interface/src/raypick/MouseTransformNode.cpp +++ b/interface/src/raypick/MouseTransformNode.cpp @@ -8,36 +8,20 @@ #include "MouseTransformNode.h" -#include "DependencyManager.h" -#include "PickManager.h" -#include "MouseRayPick.h" - -const PickFilter MOUSE_TRANSFORM_NODE_PICK_FILTER( - 1 << PickFilter::PICK_ENTITIES | - 1 << PickFilter::PICK_AVATARS | - 1 << PickFilter::PICK_INCLUDE_NONCOLLIDABLE -); -const float MOUSE_TRANSFORM_NODE_MAX_DISTANCE = 1000.0f; - -MouseTransformNode::MouseTransformNode() { - _parentMouseRayPick = DependencyManager::get()->addPick(PickQuery::Ray, - std::make_shared(MOUSE_TRANSFORM_NODE_PICK_FILTER, MOUSE_TRANSFORM_NODE_MAX_DISTANCE, true)); -} - -MouseTransformNode::~MouseTransformNode() { - if (DependencyManager::isSet()) { - auto pickManager = DependencyManager::get(); - if (pickManager) { - pickManager->removePick(_parentMouseRayPick); - } - } -} +#include "Application.h" +#include "display-plugins/CompositorHelper.h" +#include "RayPick.h" Transform MouseTransformNode::getTransform() { - Transform transform; - std::shared_ptr rayPickResult = DependencyManager::get()->getPrevPickResultTyped(_parentMouseRayPick); - if (rayPickResult) { - transform.setTranslation(rayPickResult->intersection); + QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition(); + if (position.isValid()) { + Transform transform; + QVariantMap posMap = position.toMap(); + PickRay pickRay = qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat()); + transform.setTranslation(pickRay.origin); + transform.setRotation(rotationBetween(Vectors::UP, pickRay.direction)); + return transform; } - return transform; + + return Transform(); } \ No newline at end of file diff --git a/interface/src/raypick/MouseTransformNode.h b/interface/src/raypick/MouseTransformNode.h index 7a05370dd7..4f340339e4 100644 --- a/interface/src/raypick/MouseTransformNode.h +++ b/interface/src/raypick/MouseTransformNode.h @@ -12,12 +12,7 @@ class MouseTransformNode : public TransformNode { public: - MouseTransformNode(); - ~MouseTransformNode(); Transform getTransform() override; - -protected: - unsigned int _parentMouseRayPick = 0; }; #endif // hifi_MouseTransformNode_h \ No newline at end of file From 6f6e70a470e10cb237a8392d23c1e242ccc6fff5 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 28 Aug 2018 08:45:47 -0700 Subject: [PATCH 113/744] Re-order variable initialization in CollisionRegion copy contructor --- libraries/shared/src/RegisteredMetaTypes.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 312e017dc8..32ecfe8b6d 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -270,10 +270,10 @@ public: CollisionRegion() { } CollisionRegion(const CollisionRegion& collisionRegion) : - threshold(collisionRegion.threshold), modelURL(collisionRegion.modelURL), shapeInfo(std::make_shared()), - transform(collisionRegion.transform) + transform(collisionRegion.transform), + threshold(collisionRegion.threshold) { shapeInfo->setParams(collisionRegion.shapeInfo->getType(), collisionRegion.shapeInfo->getHalfExtents(), collisionRegion.modelURL.toString()); } From 63eb57c741c352e86caaa59515e203a3e8f6e13c Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Tue, 28 Aug 2018 08:58:25 -0700 Subject: [PATCH 114/744] Exploring PID again --- interface/src/LODManager.cpp | 73 +++++++++++++++++++--- interface/src/LODManager.h | 21 ++++++- libraries/render/src/render/Args.h | 2 + scripts/developer/utilities/render/lod.qml | 53 ++++++++++++++++ 4 files changed, 140 insertions(+), 9 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index cd692bccf0..91a547cf31 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -75,9 +75,41 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { return; } + auto Kp = _pid.x; + auto Ki = _pid.y; + auto Kd = _pid.z; + float oldOctreeSizeScale = _octreeSizeScale; + float oldSolidAngle = getSolidAngle(); + + float targetFPS = getLODDecreaseFPS(); + float targetPeriod = 1.0f / targetFPS; + float currentFPS = (float)MSECS_PER_SECOND / _avgRenderTime; + static uint64_t lastTime = usecTimestampNow(); uint64_t now = usecTimestampNow(); + auto dt = (float) ((now - lastTime) / double(USECS_PER_MSEC)); + if (dt < targetPeriod * _pid.w) return; + + float deltaFPS = currentFPS - getLODDecreaseFPS(); + + lastTime = now; + auto previous_error = 0.f; + auto integral = 0.f; + + auto error = getLODDecreaseFPS() - currentFPS; + integral = integral + error * dt; + auto derivative = (error - previous_error) / dt; + auto output = Kp * error + Ki * integral + Kd * derivative; + previous_error = error; + + auto newSolidAngle = std::max( 0.1f, std::min(output, 45.f)); + + auto halTan = glm::tan(glm::radians(newSolidAngle * 0.5f)); + + auto octreeSizeScale = TREE_SCALE * OCTREE_TO_MESH_RATIO / halTan; + _octreeSizeScale = octreeSizeScale; +/* if (currentFPS < getLODDecreaseFPS()) { if (now > _decreaseFPSExpiry) { _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; @@ -123,7 +155,7 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; _decreaseFPSExpiry = _increaseFPSExpiry; } - +*/ if (oldOctreeSizeScale != _octreeSizeScale) { auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); if (lodToolsDialog) { @@ -243,7 +275,7 @@ bool LODManager::shouldRender(const RenderArgs* args, const AABox& bounds) { auto pos = args->getViewFrustum().getPosition() - bounds.calcCenter(); auto dim = bounds.getDimensions(); auto halfTanSq = 0.25f * glm::dot(dim, dim) / glm::dot(pos, pos); - return (halfTanSq >= args->_solidAngleHalfTan * args->_solidAngleHalfTan); + return (halfTanSq >= args->_solidAngleHalfTanSq); }; @@ -279,10 +311,10 @@ void LODManager::setWorldDetailQuality(float quality) { bool isHMDMode = qApp->isHMDMode(); float maxFPS = isHMDMode ? MAX_HMD_FPS : MAX_DESKTOP_FPS; - float desiredFPS = maxFPS - THRASHING_DIFFERENCE; + float desiredFPS = maxFPS /* - THRASHING_DIFFERENCE*/; if (!isLowestValue) { - float calculatedFPS = (maxFPS - (maxFPS * quality)) - THRASHING_DIFFERENCE; + float calculatedFPS = (maxFPS - (maxFPS * quality))/* - THRASHING_DIFFERENCE*/; desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS; } @@ -315,14 +347,15 @@ float LODManager::getWorldDetailQuality() const { increaseFPS = getDesktopLODDecreaseFPS(); } float maxFPS = inHMD ? MAX_HMD_FPS : MAX_DESKTOP_FPS; - float percentage = increaseFPS / maxFPS; + float percentage = 1.0 - increaseFPS / maxFPS; - if (percentage >= HIGH) { + if (percentage <= LOW) { return LOW; } - else if (percentage >= LOW) { + else if (percentage <= MEDIUM) { return MEDIUM; } + return HIGH; } @@ -334,3 +367,29 @@ float LODManager::getSolidAngleHalfTan() const { float LODManager::getSolidAngle() const { return glm::degrees(2.0 * atan(getSolidAngleHalfTan())); } + + +float LODManager::getPidKp() const { + return _pid.x; +} +float LODManager::getPidKi() const { + return _pid.y; +} +float LODManager::getPidKd() const { + return _pid.z; +} +float LODManager::getPidT() const { + return _pid.w; +} +void LODManager::setPidKp(float k) { + _pid.x = k; +} +void LODManager::setPidKi(float k) { + _pid.y = k; +} +void LODManager::setPidKd(float k) { + _pid.z = k; +} +void LODManager::setPidT(float t) { + _pid.w = t; +} diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 340292c579..2251281ae2 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -23,8 +23,8 @@ const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f; const float DEFAULT_HMD_LOD_DOWN_FPS = 34.0f; const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec -const float MAX_LIKELY_DESKTOP_FPS = 59.0f; // this is essentially, V-synch - 1 fps -const float MAX_LIKELY_HMD_FPS = 74.0f; // this is essentially, V-synch - 1 fps +const float MAX_LIKELY_DESKTOP_FPS = 61.0f; // this is essentially, V-synch - 1 fps +const float MAX_LIKELY_HMD_FPS = 91.0f; // this is essentially, V-synch - 1 fps const float INCREASE_LOD_GAP_FPS = 10.0f; // fps // The default value DEFAULT_OCTREE_SIZE_SCALE means you can be 400 meters away from a 1 meter object in order to see it (which is ~20:20 vision). @@ -72,6 +72,12 @@ class LODManager : public QObject, public Dependency { Q_PROPERTY(float solidAngleHalfTan READ getSolidAngleHalfTan) Q_PROPERTY(float solidAngle READ getSolidAngle) + Q_PROPERTY(float pidKp READ getPidKp WRITE setPidKp) + Q_PROPERTY(float pidKi READ getPidKi WRITE setPidKi) + Q_PROPERTY(float pidKd READ getPidKd WRITE setPidKd) + Q_PROPERTY(float pidT READ getPidT WRITE setPidT) + + public: /**jsdoc @@ -190,6 +196,15 @@ public: float getSolidAngleHalfTan() const; float getSolidAngle() const; + float getPidKp() const; + float getPidKi() const; + float getPidKd() const; + float getPidT() const; + void setPidKp(float k); + void setPidKi(float k); + void setPidKd(float k); + void setPidT(float t); + signals: /**jsdoc @@ -222,6 +237,8 @@ private: float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; + glm::vec4 _pid{ 0.1f, 0.0f, 0.0f, 3.0f }; + uint64_t _decreaseFPSExpiry { 0 }; uint64_t _increaseFPSExpiry { 0 }; }; diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index a46b914826..589945e9c7 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -81,6 +81,7 @@ namespace render { _context(context), _sizeScale(sizeScale), _solidAngleHalfTan(solidAngleHalfTan), + _solidAngleHalfTanSq(solidAngleHalfTan * solidAngleHalfTan), _boundaryLevelAdjust(boundaryLevelAdjust), _renderMode(renderMode), _displayMode(displayMode), @@ -111,6 +112,7 @@ namespace render { float _sizeScale { 1.0f }; int _boundaryLevelAdjust { 0 }; float _solidAngleHalfTan{ 0.1f }; + float _solidAngleHalfTanSq{ _solidAngleHalfTan * _solidAngleHalfTan }; RenderMode _renderMode { DEFAULT_RENDER_MODE }; DisplayMode _displayMode { MONO }; diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index c08c089bad..742e8c0dc1 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -78,6 +78,59 @@ Item { anchors.left: parent.left anchors.right: parent.right } + + RichSlider { + visible: LODManager.automaticLODAdjust + showLabel: true + label: "LOD PID Kp" + valueVar: LODManager["pidKp"] + valueVarSetter: (function (v) { LODManager["pidKp"] = v }) + max: 1.0 + min: 0.0 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: LODManager.automaticLODAdjust + showLabel: true + label: "LOD PID Ki" + valueVar: LODManager["pidKi"] + valueVarSetter: (function (v) { LODManager["pidKi"] = v }) + max: 1.0 + min: 0.0 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: LODManager.automaticLODAdjust + showLabel: true + label: "LOD PID Kd" + valueVar: LODManager["pidKd"] + valueVarSetter: (function (v) { LODManager["pidKd"] = v }) + max: 1.0 + min: 0.0 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: LODManager.automaticLODAdjust + showLabel: true + label: "LOD PID Num T" + valueVar: LODManager["pidT"] + valueVarSetter: (function (v) { LODManager["pidT"] = v }) + max: 5.0 + min: 0.0 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } } Column { From 027d11736446769867807294f6e842463ee4889a Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 28 Aug 2018 09:41:09 -0700 Subject: [PATCH 115/744] Remove deadlock caused by trying readlock on NodeList Also just use refs to other SharedNodes as the NodeList is locked. --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 2ba54bd6b6..50ddcec92b 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -288,13 +288,19 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes struct AvatarSortData { - SharedNodePointer _nodeShared; + AvatarSortData(const SharedNodePointer& nodeShared, AvatarData* avatarData, quint64 lastEncodeTime) + : _nodeShared(nodeShared) + , _node(nodeShared.data()) + , _avatarData(avatarData) + , _lastEncodeTime(lastEncodeTime) + { } + const SharedNodePointer& _nodeShared; Node* _node; AvatarData* _avatarData; quint64 _lastEncodeTime; }; + // Temporary info about the avatars we're sending: std::vector avatarsToSort; - avatarsToSort.reserve(nodeList->size()); //std::unordered_map avatarDataToNodes; //std::unordered_map avatarDataToNodesShared; //std::unordered_map avatarEncodeTimes; @@ -311,7 +317,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) //avatarDataToNodes[otherAvatar] = otherNode.data(); //avatarDataToNodesShared[otherAvatar] = otherNode; auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(otherAvatar->getSessionUUID()); - avatarsToSort.emplace_back(AvatarSortData({ otherNode, otherNodeRaw, otherAvatar, lastEncodeTime })); + avatarsToSort.emplace_back(AvatarSortData(otherNode, otherAvatar, lastEncodeTime)); } }); @@ -345,7 +351,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarData::_avatarSortCoefficientAge); // ignore or sort - const AvatarData* thisAvatar = nodeData->getAvatarSharedPointer().get(); for (const auto& avatar : avatarsToSort) { if (avatar._node == nodeRaw) { // don't echo updates to self @@ -437,7 +442,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); while (!sortedAvatars.empty()) { - const AvatarData* avatarData = sortedAvatars.top().getAvatar(); const Node* otherNode = sortedAvatars.top().getNode(); sortedAvatars.pop(); remainingAvatars--; From c4d43b3fbc5238ff50838faba1e02ea6d0f4df41 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 28 Aug 2018 19:43:59 +0200 Subject: [PATCH 116/744] CR fixes --- interface/resources/qml/InteractiveWindow.qml | 2 +- libraries/ui/src/MainWindow.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index 1c41dd189b..e8ddbf823d 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -147,7 +147,7 @@ Windows.Window { Qt.WindowMaximizeButtonHint | Qt.WindowMinimizeButtonHint; // only use the always on top feature for non Windows OS - if (Qt.platform.os !== "windows" && (flags & Desktop.ALWAYS_ON_TOP) === Desktop.ALWAYS_ON_TOP) { + if (Qt.platform.os !== "windows" && (flags & Desktop.ALWAYS_ON_TOP)) { nativeWindowFlags |= Qt.WindowStaysOnTopHint; } nativeWindow.flags = nativeWindowFlags; diff --git a/libraries/ui/src/MainWindow.cpp b/libraries/ui/src/MainWindow.cpp index 1a13194974..680433b2f9 100644 --- a/libraries/ui/src/MainWindow.cpp +++ b/libraries/ui/src/MainWindow.cpp @@ -43,7 +43,7 @@ MainWindow::~MainWindow() { QWindow* MainWindow::findMainWindow() { auto windows = qApp->topLevelWindows(); QWindow* result = nullptr; - for (auto window : windows) { + for (const auto& window : windows) { if (window->objectName().contains("MainWindow")) { result = window; break; From 7dfdc796448e23c5f231a3c319fe64c94fa1abdb Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Tue, 28 Aug 2018 12:21:15 -0700 Subject: [PATCH 117/744] Picking pid values --- interface/src/LODManager.cpp | 5 ++++- interface/src/LODManager.h | 2 +- scripts/developer/utilities/render/lod.qml | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 91a547cf31..411e0156b5 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -103,7 +103,10 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { auto output = Kp * error + Ki * integral + Kd * derivative; previous_error = error; - auto newSolidAngle = std::max( 0.1f, std::min(output, 45.f)); + + auto newSolidAngle = oldSolidAngle + output; + + newSolidAngle = std::max( 0.5f, std::min(newSolidAngle, 90.f)); auto halTan = glm::tan(glm::radians(newSolidAngle * 0.5f)); diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 2251281ae2..b2e324a820 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -237,7 +237,7 @@ private: float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; - glm::vec4 _pid{ 0.1f, 0.0f, 0.0f, 3.0f }; + glm::vec4 _pid{ 0.06f, 0.005f, 0.01f, 4.0f }; uint64_t _decreaseFPSExpiry { 0 }; uint64_t _increaseFPSExpiry { 0 }; diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 742e8c0dc1..1ca9b68de2 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -98,7 +98,7 @@ Item { label: "LOD PID Ki" valueVar: LODManager["pidKi"] valueVarSetter: (function (v) { LODManager["pidKi"] = v }) - max: 1.0 + max: 0.02 min: 0.0 integral: false @@ -111,7 +111,7 @@ Item { label: "LOD PID Kd" valueVar: LODManager["pidKd"] valueVarSetter: (function (v) { LODManager["pidKd"] = v }) - max: 1.0 + max: 0.1 min: 0.0 integral: false From 49f5d5f2b7dd7fccb1a7bfc73733c12daf28b431 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Aug 2018 08:55:46 +1200 Subject: [PATCH 118/744] Display mini tablet rotated in palm --- scripts/system/miniTablet.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index ca665b888a..25a3389539 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -22,12 +22,12 @@ PROXY_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0046 }, // Proportional to tablet proper. PROXY_POSITION_LEFT_HAND = { x: 0, - y: 0.07, // Distance from joint. + y: 0.1, // Distance from joint. z: 0.07 // Distance above palm. }, PROXY_POSITION_RIGHT_HAND = { x: 0, - y: 0.07, // Distance from joint. + y: 0.1, // Distance from joint. z: 0.07 // Distance above palm. }, /* @@ -36,8 +36,8 @@ PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: -90 }), */ // Aligned with palm. - PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), - PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: -40, y: 180, z: 0 }), + PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: -40, y: 180, z: 0 }), // UI overlay. proxyUIOverlay = null, @@ -79,7 +79,7 @@ { x: 0.5, y: -0.75, z: 0 }, { x: -0.5, y: -0.75, z: 0 } ], - PROXY_EXPAND_DELTA_ROTATION = Quat.fromVec3Degrees({ x: -45, y: 0, z: 0 }), + PROXY_EXPAND_DELTA_ROTATION = Quat.fromVec3Degrees({ x: -5, y: 0, z: 0 }), proxyExpandHand, proxyExpandLocalPosition, proxyExpandLocalRotation = Quat.IDENTITY, From f5c49026abebf14d166a7c6ef2bc5f0e545141f4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Aug 2018 09:49:42 +1200 Subject: [PATCH 119/744] Expand tablet after it's been released from grab by other hand --- scripts/system/miniTablet.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 25a3389539..c7f04cca11 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -64,10 +64,11 @@ PROXY_HIDING = 2, PROXY_SHOWING = 3, PROXY_VISIBLE = 4, - PROXY_EXPANDING = 5, - TABLET_OPEN = 6, - STATE_STRINGS = ["PROXY_DISABLED", "PROXY_HIDDEN", "PROXY_HIDING", "PROXY_SHOWING", "PROXY_VISIBLE", "PROXY_EXPANDING", - "TABLET_OPEN"], + PROXY_GRABBED = 5, + PROXY_EXPANDING = 6, + TABLET_OPEN = 7, + STATE_STRINGS = ["PROXY_DISABLED", "PROXY_HIDDEN", "PROXY_HIDING", "PROXY_SHOWING", "PROXY_VISIBLE", "PROXY_GRABBED", + "PROXY_EXPANDING", "TABLET_OPEN"], STATE_MACHINE, rezzerState = PROXY_DISABLED, proxyHand, @@ -513,6 +514,13 @@ } } + function updateProxyGrabbed() { + // Hide proxy if tablet has been displayed by other means. + if (HMD.showTablet) { + setState(PROXY_HIDDEN); + } + } + function expandProxy() { var scaleFactor = (Date.now() - proxyExpandStart) / PROXY_EXPAND_DURATION; if (scaleFactor < 1) { @@ -598,7 +606,12 @@ update: updateProxyVisible, exit: null }, - PROXY_EXPANDING: { // Tablet proxy has been grabbed and is expanding before showing tablet proper. + PROXY_GRABBED: { // Tablet proxy is grabbed by other hand. + enter: null, + update: updateProxyGrabbed, + exit: null + }, + PROXY_EXPANDING: { // Tablet proxy is expanding before showing tablet proper. enter: enterProxyExpanding, update: updateProxyExanding, exit: exitProxyExpanding @@ -655,6 +668,13 @@ } if (message.action === "grab" && rezzerState === PROXY_VISIBLE) { + hand = message.joint === HAND_NAMES[proxyHand] ? proxyHand : otherHand(proxyHand); + if (hand === proxyHand) { + setState(PROXY_EXPANDING, hand); + } else { + setState(PROXY_GRABBED); + } + } else if (message.action === "release" && rezzerState === PROXY_GRABBED) { hand = message.joint === HAND_NAMES[proxyHand] ? proxyHand : otherHand(proxyHand); setState(PROXY_EXPANDING, hand); } From 91c1e1b27679f44b118c350a3f5d6be4465a3714 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Aug 2018 09:59:04 +1200 Subject: [PATCH 120/744] Different expansion points and delta rotations if grabbed by other hand --- scripts/system/miniTablet.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index c7f04cca11..efe33303a8 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -81,7 +81,14 @@ { x: -0.5, y: -0.75, z: 0 } ], PROXY_EXPAND_DELTA_ROTATION = Quat.fromVec3Degrees({ x: -5, y: 0, z: 0 }), + PROXY_EXPAND_HANDLES_OTHER = [ // Different handles when expanding after being grabbed by other hand, + { x: 0.5, y: -0.4, z: 0 }, + { x: -0.5, y: -0.4, z: 0 } + ], + PROXY_EXPAND_DELTA_ROTATION_OTHER = Quat.IDENTITY, proxyExpandHand, + proxyExpandHandles = PROXY_EXPAND_HANDLES, + proxyExpandDeltaRotation = PROXY_EXPAND_HANDLES_OTHER, proxyExpandLocalPosition, proxyExpandLocalRotation = Quat.IDENTITY, PROXY_EXPAND_DURATION = 250, @@ -331,7 +338,7 @@ Vec3.sum(proxyExpandLocalPosition, Vec3.multiplyQbyV(proxyExpandLocalRotation, Vec3.multiply(-tabletScaleFactor, - Vec3.multiplyVbyV(PROXY_EXPAND_HANDLES[proxyExpandHand], PROXY_DIMENSIONS))) + Vec3.multiplyVbyV(proxyExpandHandles[proxyExpandHand], PROXY_DIMENSIONS))) ); localPosition = Vec3.sum(localPosition, Vec3.multiplyQbyV(proxyExpandLocalRotation, { x: 0, y: 0.5 * -dimensions.y, z: 0 })); @@ -533,18 +540,27 @@ } function enterProxyExpanding(hand) { + // Expansion details. + if (hand === proxyHand) { + proxyExpandHandles = PROXY_EXPAND_HANDLES; + proxyExpandDeltaRotation = PROXY_EXPAND_DELTA_ROTATION; + } else { + proxyExpandHandles = PROXY_EXPAND_HANDLES_OTHER; + proxyExpandDeltaRotation = PROXY_EXPAND_DELTA_ROTATION_OTHER; + } + // Grab details. var properties = Overlays.getProperties(proxyOverlay, ["localPosition", "localRotation"]); proxyExpandHand = hand; proxyExpandLocalRotation = properties.localRotation; proxyExpandLocalPosition = Vec3.sum(properties.localPosition, Vec3.multiplyQbyV(proxyExpandLocalRotation, - Vec3.multiplyVbyV(PROXY_EXPAND_HANDLES[proxyExpandHand], PROXY_DIMENSIONS))); + Vec3.multiplyVbyV(proxyExpandHandles[proxyExpandHand], PROXY_DIMENSIONS))); // Start expanding. proxyInitialWidth = PROXY_DIMENSIONS.x; proxyTargetWidth = getTabletWidthFromSettings(); - proxyTargetLocalRotation = Quat.multiply(proxyExpandLocalRotation, PROXY_EXPAND_DELTA_ROTATION); + proxyTargetLocalRotation = Quat.multiply(proxyExpandLocalRotation, proxyExpandDeltaRotation); proxyExpandStart = Date.now(); proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); } From 8a9ad421e7fd69338e74236dea9a629519f35ad1 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 28 Aug 2018 15:50:14 -0700 Subject: [PATCH 121/744] adding serverless file + redirection if conn fail --- interface/CMakeLists.txt | 5 +++ interface/resources/serverless/redirect.json | 1 + interface/src/Application.cpp | 33 ++++++++++++++++++++ interface/src/Application.h | 1 + libraries/networking/src/AddressManager.cpp | 2 +- libraries/networking/src/AddressManager.h | 1 + 6 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 interface/resources/serverless/redirect.json diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 990d84a774..cd058add94 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -341,6 +341,8 @@ else() set(INTERFACE_EXEC_DIR "$") set(RESOURCES_DEV_DIR "${INTERFACE_EXEC_DIR}/resources") + message(STATUS "${RESOURCES_DEV_DIR}") + # copy the resources files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different @@ -360,6 +362,9 @@ else() COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${PROJECT_SOURCE_DIR}/resources/serverless/tutorial.json" "${RESOURCES_DEV_DIR}/serverless/tutorial.json" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${PROJECT_SOURCE_DIR}/resources/serverless/redirect.json" + "${RESOURCES_DEV_DIR}/serverless/redirect.json" # copy JSDoc files beside the executable COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/tools/jsdoc/out" diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json new file mode 100644 index 0000000000..d97f44220a --- /dev/null +++ b/interface/resources/serverless/redirect.json @@ -0,0 +1 @@ +{"DataVersion": 0, "Entities": [{"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{9a50eca4-5c97-4e21-8cb9-5197bb09770e}", "lastEdited": 1535474935077402, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{2d90745c-2fe2-4f9c-9dcf-b715f7a9d0c0}", "lastEdited": 1535474935076550, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{dcbca0fd-e1d8-4248-b2b4-b91346224876}", "lastEdited": 1535474935077745, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.927593469619751, "x": -3.072406530380249, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.479954719543457}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{3df15ffb-9614-4ec1-81a8-e278d1917748}", "lastEdited": 1535474935076889, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{fd138f98-d436-41d2-863a-cbef6e41e1cb}", "lastEdited": 1535474935078215, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{b2a90f9e-a5ec-4faa-a6a3-bc457516f5ee}", "lastEdited": 1535474935077505, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{5c9ef440-f022-4d21-9f00-22827851b522}", "lastEdited": 1535474935077201, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.948914527893066, "green": 0.16741032898426056, "red": 0.9275782108306885, "x": -3.0724217891693115, "y": -9.33258967101574, "z": 1.9489145278930664}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904473781585693, "y": 1.028862476348877, "z": -2.479969024658203}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{2fe2296a-d74d-4ee7-bf7d-c9665bc63237}", "lastEdited": 1535474935076656, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{86f1ba10-2921-45ad-bb74-dcbe5c49bc69}", "lastEdited": 1535474935077298, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{1023380c-13dd-445d-bb57-1e02574e5cb3}", "lastEdited": 1535474935076124, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{d5d841b2-0cc0-4ab2-84fe-dce3d20f6b04}", "lastEdited": 1535474935077609, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.927593469619751, "x": -3.072406530380249, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.479954719543457}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{02da6c73-bab0-4e7e-b8fc-a9c0500ac66e}", "lastEdited": 1535474935075994, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{f1c2110e-1429-49b2-949d-6c73718b2d65}", "lastEdited": 1535474935078059, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{1e09084f-c0d7-4bd4-a78d-290c03391579}", "lastEdited": 1535474935076234, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.927593469619751, "x": -3.072406530380249, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.479954719543457}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{e8282a17-76a5-4cd8-9f0e-473f097c8ad1}", "lastEdited": 1535474935077902, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{46fbad8a-5bc0-44c8-a9ca-beb38f672d2a}", "lastEdited": 1535474935076994, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{2861b544-1c03-439a-85ad-3758ba87ca55}", "lastEdited": 1535474935076339, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{59b7fc7f-0100-4115-a9f8-55d5b45da540}", "lastEdited": 1535474935077098, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{2888e4b0-6eb1-4468-9b95-893dcb3e99a5}", "lastEdited": 1535474935076443, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{36f00cad-64b0-415f-9f70-e92cd85836d8}", "lastEdited": 1535474935076783, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": false, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 14.40000057220459, "green": 14.40000057220459, "red": 14.40000057220459, "x": 14.40000057220459, "y": 14.40000057220459, "z": 14.40000057220459}, "id": "{2cefd6b6-6a00-49c3-87c8-71b9c83f96f1}", "lastEdited": 1535474935056115, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 2.3440732955932617, "green": 1.7684326171875, "red": 1.8812973499298096, "x": -2.1187026500701904, "y": -7.7315673828125, "z": -1.6559267044067383}, "queryAACube": {"scale": 24.9415340423584, "x": -10.589469909667969, "y": -10.7023344039917, "z": -10.126693725585938}, "rotation": {"w": 0.8697794675827026, "x": -1.52587890625e-05, "y": 0.4933699369430542, "z": -4.57763671875e-05}, "shapeType": "box", "skyboxMode": "enabled", "type": "Zone", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"clientOnly": false, "color": {"blue": 0, "green": 0, "red": 0}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 11.117486953735352, "green": 3.580313205718994, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 3.580313205718994, "z": 11.117486953735352}, "id": "{efde7819-3a7a-4984-ac3e-28bcf69c6b1e}", "lastEdited": 1535474935053285, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 0, "green": 1.1583251953125, "red": 4.971565246582031, "x": 0.9715652465820312, "y": -8.3416748046875, "z": -4}, "queryAACube": {"scale": 11.681488037109375, "x": -0.8691787719726562, "y": -4.6824188232421875, "z": -5.8407440185546875}, "rotation": {"w": 0.8637980222702026, "x": -4.57763671875e-05, "y": 0.5038070678710938, "z": -1.52587890625e-05}, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": false, "color": {"blue": 0, "green": 0, "red": 0}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 11.117486953735352, "green": 3.580313205718994, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 3.580313205718994, "z": 11.117486953735352}, "id": "{fe8d6b12-3697-4fcd-9092-af5b7f49a7f5}", "lastEdited": 1535474947253763, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 5.268576622009277, "green": 1.1588134765625, "red": 6.100250244140625, "x": 2.100250244140625, "y": -8.3411865234375, "z": 1.2685766220092773}, "queryAACube": {"scale": 11.681488037109375, "x": 0.2595062255859375, "y": -4.6819305419921875, "z": -0.5721673965454102}, "rotation": {"w": 0.9662165641784668, "x": -4.57763671875e-05, "y": -0.2576791048049927, "z": 1.52587890625e-05}, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": false, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.06014331430196762, "green": 2.582186460494995, "red": 2.582186698913574, "x": 2.582186698913574, "y": 2.582186460494995, "z": 0.06014331430196762}, "id": "{c8f9b1b1-3bd7-473c-8c2a-a8644a7acdd7}", "lastEdited": 1535474935054058, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/oopsDialog.fbx", "name": "Oops Dialog", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 1.45927095413208, "green": 1.6763916015625, "red": 0, "x": -4, "y": -7.8236083984375, "z": -2.54072904586792}, "queryAACube": {"scale": 3.6522583961486816, "x": -1.8261291980743408, "y": -0.14973759651184082, "z": -0.36685824394226074}, "rotation": {"w": 0.8684672117233276, "x": -4.57763671875e-05, "y": 0.4957197904586792, "z": -7.62939453125e-05}, "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"angularDamping": 0, "clientOnly": false, "color": {"blue": 0, "green": 0, "red": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.20000000298023224, "green": 0.20000000298023224, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 0.20000000298023224, "z": 0.20000000298023224}, "id": "{d0b0491a-5c7d-4cd2-9ebe-492b709a702b}", "lastEdited": 1535474935052912, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 3.8216662406921387, "green": 0, "red": 1.2409718036651611, "x": -2.759028196334839, "y": -9.5, "z": -0.17833375930786133}, "queryAACube": {"scale": 0.3464101552963257, "x": 1.0677666664123535, "y": -0.17320507764816284, "z": 3.648461103439331}, "rotation": {"w": 0.6444342136383057, "x": -0.08220034837722778, "y": -0.6649118661880493, "z": 0.3684900999069214}, "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"angularDamping": 0, "clientOnly": false, "color": {"blue": 0, "green": 0, "red": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.20000000298023224, "green": 0.20000000298023224, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 0.20000000298023224, "z": 0.20000000298023224}, "id": "{ae4a84ee-4e95-4e6b-b63d-399893a9b3f9}", "lastEdited": 1535474935052114, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 1.3116319179534912, "green": 0, "red": 2.705613613128662, "x": -1.294386386871338, "y": -9.5, "z": -2.688368082046509}, "queryAACube": {"scale": 0.3464101552963257, "x": 2.5324084758758545, "y": -0.17320507764816284, "z": 1.1384267807006836}, "rotation": {"w": 0.9127794504165649, "x": 0.2575265169143677, "y": 0.15553522109985352, "z": 0.2761729955673218}, "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"angularDamping": 0, "clientOnly": false, "color": {"blue": 0, "green": 0, "red": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.20000000298023224, "green": 0.20000000298023224, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 0.20000000298023224, "z": 0.20000000298023224}, "id": "{c1404297-9c6a-4d2d-b91d-c844ddd391db}", "lastEdited": 1535474935051740, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 3.8216662406921387, "green": 0.0772705078125, "red": 1.2409718036651611, "x": -2.759028196334839, "y": -9.4227294921875, "z": -0.17833375930786133}, "queryAACube": {"scale": 0.3464101552963257, "x": 1.0677666664123535, "y": -0.09593456983566284, "z": 3.648461103439331}, "rotation": {"w": 0.926024317741394, "x": 0.20308232307434082, "y": -0.010269343852996826, "z": 0.3179827928543091}, "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": false, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 9.030570983886719, "green": 0.0719478651881218, "red": 9.030570983886719, "x": 9.030570983886719, "y": 0.0719478651881218, "z": 9.030570983886719}, "id": "{f17920f3-1571-4c5e-b002-e328cf202037}", "lastEdited": 1535474950564649, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/floor.fbx", "name": "Floor", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 4.2324604988098145, "green": 0.17547607421875, "red": 4.802988529205322, "x": 0.8029885292053223, "y": -9.32452392578125, "z": 0.23246049880981445}, "queryAACube": {"scale": 12.771358489990234, "x": -1.582690715789795, "y": -6.210203170776367, "z": -2.1532187461853027}, "rotation": {"w": 0.8648051023483276, "x": -1.52587890625e-05, "y": 0.5020675659179688, "z": -4.57763671875e-05}, "shapeType": "simple-hull", "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"alpha": 0, "alphaFinish": 0, "alphaStart": 0.25, "clientOnly": false, "colorFinish": {"blue": 0, "green": 0, "red": 0, "x": 0, "y": 0, "z": 0}, "colorStart": {"blue": 255, "green": 255, "red": 255, "x": 255, "y": 255, "z": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 13.24000072479248, "green": 13.24000072479248, "red": 13.24000072479248, "x": 13.24000072479248, "y": 13.24000072479248, "z": 13.24000072479248}, "emitAcceleration": {"blue": 0, "green": 0.10000000149011612, "red": 0, "x": 0, "y": 0.10000000149011612, "z": 0}, "emitDimensions": {"blue": 1, "green": 1, "red": 1, "x": 1, "y": 1, "z": 1}, "emitOrientation": {"w": 1, "x": -1.52587890625e-05, "y": -1.52587890625e-05, "z": -1.52587890625e-05}, "emitRate": 6, "emitSpeed": 0, "id": "{7561445a-4f9c-41aa-96b9-b8ce8ad686d3}", "lastEdited": 1535474935056499, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "lifespan": 10, "maxParticles": 10, "name": "Stars", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "particleRadius": 0.07000000029802322, "polarFinish": 3.1415927410125732, "position": {"blue": 1.3712034225463867, "green": 0.5220947265625, "red": 2.6281180381774902, "x": -1.3718819618225098, "y": -8.9779052734375, "z": -2.6287965774536133}, "queryAACube": {"scale": 22.932353973388672, "x": -8.838058471679688, "y": -10.944082260131836, "z": -10.09497356414795}, "radiusFinish": 0, "radiusStart": 0, "rotation": {"w": 0.9852597713470459, "x": -1.52587890625e-05, "y": -0.17106890678405762, "z": -7.62939453125e-05}, "speedSpread": 0, "spinFinish": null, "spinStart": null, "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"alpha": 0, "alphaFinish": 0, "alphaStart": 1, "clientOnly": false, "color": {"blue": 255, "green": 205, "red": 3}, "colorFinish": {"blue": 0, "green": 0, "red": 0, "x": 0, "y": 0, "z": 0}, "colorStart": {"blue": 255, "green": 204, "red": 0, "x": 0, "y": 204, "z": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 2.5, "green": 2.5, "red": 2.5, "x": 2.5, "y": 2.5, "z": 2.5}, "emitAcceleration": {"blue": 0, "green": 0, "red": 0, "x": 0, "y": 0, "z": 0}, "emitDimensions": {"blue": 1, "green": 1, "red": 1, "x": 1, "y": 1, "z": 1}, "emitOrientation": {"w": 0.9993909597396851, "x": 0.034897372126579285, "y": -1.525880907138344e-05, "z": -1.525880907138344e-05}, "emitRate": 2, "emitSpeed": 0, "emitterShouldTrail": true, "id": "{7c59b77f-2f88-4c35-899b-3e42bd486ed6}", "lastEdited": 1535474935050547, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "lifespan": 10, "maxParticles": 40, "name": "Rays", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "particleRadius": 0.75, "polarFinish": 3.1415927410125732, "position": {"blue": 3.814434051513672, "green": 1.44122314453125, "red": 1.2319090366363525, "x": -2.7680909633636475, "y": -8.05877685546875, "z": -0.18556594848632812}, "queryAACube": {"scale": 4.330127239227295, "x": -0.9331545829772949, "y": -0.7238404750823975, "z": 1.6493704319000244}, "radiusFinish": 0.10000000149011612, "radiusStart": 0, "rotation": {"w": 0.9594720602035522, "x": -1.52587890625e-05, "y": 0.28178834915161133, "z": -4.57763671875e-05}, "speedSpread": 0, "spinFinish": null, "spinStart": null, "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"angularDamping": 0, "clientOnly": false, "color": {"blue": 0, "green": 0, "red": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.20000000298023224, "green": 0.20000000298023224, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 0.20000000298023224, "z": 0.20000000298023224}, "id": "{55dd2b28-1c53-431b-b2ea-61155dfc6652}", "lastEdited": 1535474935055239, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 1.3116319179534912, "green": 0.15618896484375, "red": 2.705613613128662, "x": -1.294386386871338, "y": -9.34381103515625, "z": -2.688368082046509}, "queryAACube": {"scale": 0.3464101552963257, "x": 2.5324084758758545, "y": -0.017016112804412842, "z": 1.1384267807006836}, "rotation": {"w": 0.46953535079956055, "x": -0.16719311475753784, "y": -0.7982757091522217, "z": 0.3380941152572632}, "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"angularDamping": 0, "clientOnly": false, "color": {"blue": 0, "green": 0, "red": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.20000000298023224, "green": 0.20000000298023224, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 0.20000000298023224, "z": 0.20000000298023224}, "id": "{f40563f7-01e3-4faf-ad1b-19cab7a386a1}", "lastEdited": 1535474935054426, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 1.3116319179534912, "green": 0.0772705078125, "red": 2.705613613128662, "x": -1.294386386871338, "y": -9.4227294921875, "z": -2.688368082046509}, "queryAACube": {"scale": 0.3464101552963257, "x": 2.5324084758758545, "y": -0.09593456983566284, "z": 1.1384267807006836}, "rotation": {"w": -0.38777750730514526, "x": -0.37337303161621094, "y": -0.8409399390220642, "z": 0.055222392082214355}, "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"alpha": 0, "alphaFinish": 0, "alphaStart": 1, "clientOnly": false, "color": {"blue": 211, "green": 227, "red": 104}, "colorFinish": {"blue": 0, "green": 0, "red": 0, "x": 0, "y": 0, "z": 0}, "colorStart": {"blue": 211, "green": 227, "red": 104, "x": 104, "y": 227, "z": 211}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 2.5, "green": 2.5, "red": 2.5, "x": 2.5, "y": 2.5, "z": 2.5}, "emitAcceleration": {"blue": 0, "green": 0, "red": 0, "x": 0, "y": 0, "z": 0}, "emitDimensions": {"blue": 1, "green": 1, "red": 1, "x": 1, "y": 1, "z": 1}, "emitOrientation": {"w": 0.9993909597396851, "x": 0.034897372126579285, "y": -1.525880907138344e-05, "z": -1.525880907138344e-05}, "emitRate": 2, "emitSpeed": 0, "id": "{c4a86707-2eaa-41f5-a1ad-3e6029178d75}", "lastEdited": 1535474935052538, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "lifespan": 10, "maxParticles": 40, "name": "Rays", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "particleRadius": 0.75, "polarFinish": 3.1415927410125732, "position": {"blue": 1.3553659915924072, "green": 1.44122314453125, "red": 2.572803497314453, "x": -1.4271965026855469, "y": -8.05877685546875, "z": -2.6446340084075928}, "queryAACube": {"scale": 4.330127239227295, "x": 0.40773987770080566, "y": -0.7238404750823975, "z": -0.8096976280212402}, "radiusFinish": 0.10000000149011612, "radiusStart": 0, "rotation": {"w": 0.9803768396377563, "x": -1.52587890625e-05, "y": 0.19707024097442627, "z": -7.62939453125e-05}, "speedSpread": 0, "spinFinish": null, "spinStart": null, "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"angularDamping": 0, "clientOnly": false, "color": {"blue": 0, "green": 0, "red": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.20000000298023224, "green": 0.20000000298023224, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 0.20000000298023224, "z": 0.20000000298023224}, "id": "{65dd9c7f-815d-4b50-ba3b-24709691aed1}", "lastEdited": 1535474935056805, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 3.8216662406921387, "green": 0.15618896484375, "red": 1.2409718036651611, "x": -2.759028196334839, "y": -9.34381103515625, "z": -0.17833375930786133}, "queryAACube": {"scale": 0.3464101552963257, "x": 1.0677666664123535, "y": -0.017016112804412842, "z": 3.648461103439331}, "rotation": {"w": 0.6747081279754639, "x": -0.06532388925552368, "y": -0.6342412233352661, "z": 0.37175559997558594}, "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": false, "color": {"blue": 0, "green": 0, "red": 0}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 11.117486953735352, "green": 3.580313205718994, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 3.580313205718994, "z": 11.117486953735352}, "id": "{4e0f3381-ac88-48ab-9378-0b7818188234}", "lastEdited": 1535474935053657, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 6.1806135177612305, "green": 1.1588134765625, "red": 1.4755167961120605, "x": -2.5244832038879395, "y": -8.3411865234375, "z": 2.1806135177612305}, "queryAACube": {"scale": 11.681488037109375, "x": -4.365227222442627, "y": -4.6819305419921875, "z": 0.33986949920654297}, "rotation": {"w": 0.8637980222702026, "x": -4.57763671875e-05, "y": 0.5038070678710938, "z": -1.52587890625e-05}, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": false, "color": {"blue": 0, "green": 0, "red": 0}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 11.117486953735352, "green": 3.580313205718994, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 3.580313205718994, "z": 11.117486953735352}, "id": "{2da3a1d6-c757-4cc3-b20c-d7ec87eedd52}", "lastEdited": 1535474935050931, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 1.9063844680786133, "green": 1.15850830078125, "red": 0.16632509231567383, "x": -3.833674907684326, "y": -8.34149169921875, "z": -2.0936155319213867}, "queryAACube": {"scale": 11.681488037109375, "x": -5.674418926239014, "y": -4.6822357177734375, "z": -3.934359550476074}, "rotation": {"w": 0.9666743278503418, "x": -4.57763671875e-05, "y": -0.2560006380081177, "z": 1.52587890625e-05}, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"alpha": 0, "alphaFinish": 0, "alphaStart": 0.25, "clientOnly": false, "colorFinish": {"blue": 0, "green": 0, "red": 0, "x": 0, "y": 0, "z": 0}, "colorStart": {"blue": 255, "green": 255, "red": 255, "x": 255, "y": 255, "z": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 13.24000072479248, "green": 13.24000072479248, "red": 13.24000072479248, "x": 13.24000072479248, "y": 13.24000072479248, "z": 13.24000072479248}, "emitAcceleration": {"blue": 0, "green": 0.10000000149011612, "red": 0, "x": 0, "y": 0.10000000149011612, "z": 0}, "emitDimensions": {"blue": 1, "green": 1, "red": 1, "x": 1, "y": 1, "z": 1}, "emitOrientation": {"w": 1, "x": -1.52587890625e-05, "y": -1.52587890625e-05, "z": -1.52587890625e-05}, "emitRate": 6, "emitSpeed": 0, "emitterShouldTrail": true, "id": "{7db57102-9a57-48d6-9937-bcec7892c3d9}", "lastEdited": 1535474935058139, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "lifespan": 10, "maxParticles": 10, "name": "Stars", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "particleRadius": 0.07000000029802322, "polarFinish": 3.1415927410125732, "position": {"blue": 3.78922963142395, "green": 0.5220947265625, "red": 1.1928560733795166, "x": -2.8071439266204834, "y": -8.9779052734375, "z": -0.2107703685760498}, "queryAACube": {"scale": 22.932353973388672, "x": -10.273321151733398, "y": -10.944082260131836, "z": -7.676947593688965}, "radiusFinish": 0, "radiusStart": 0, "rotation": {"w": 0.996429443359375, "x": -1.52587890625e-05, "y": -0.08442819118499756, "z": -4.57763671875e-05}, "speedSpread": 0, "spinFinish": null, "spinStart": null, "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"clientOnly": false, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 2.1097896099090576, "green": 0.04847164824604988, "red": 1.458284616470337, "x": 1.458284616470337, "y": 0.04847164824604988, "z": 2.1097896099090576}, "id": "{434940be-0b45-4ca2-97db-91e698c20bfd}", "lastEdited": 1535474935055655, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal2.fbx", "name": "Back", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 1.5835940837860107, "green": 0.2467041015625, "red": 3.0345542430877686, "x": -0.9654457569122314, "y": -9.2532958984375, "z": -2.4164059162139893}, "queryAACube": {"scale": 2.5651814937591553, "x": 1.751963496208191, "y": -1.0358866453170776, "z": 0.3010033369064331}, "rotation": {"w": 0.9084458351135254, "x": -1.52587890625e-05, "y": 0.4179598093032837, "z": -0.0001068115234375}, "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":true}}"}, {"clientOnly": false, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 2.1097896099090576, "green": 0.04847164824604988, "red": 1.458284616470337, "x": 1.458284616470337, "y": 0.04847164824604988, "z": 2.1097896099090576}, "id": "{5f853b12-33b9-4f40-92a8-4506047fdcd8}", "lastEdited": 1535474935051356, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal1.fbx", "name": "Try Again", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 3.946338653564453, "green": 0.2467041015625, "red": 1.6013128757476807, "x": -2.3986871242523193, "y": -9.2532958984375, "z": -0.053661346435546875}, "queryAACube": {"scale": 2.5651814937591553, "x": 0.318722128868103, "y": -1.0358866453170776, "z": 2.663747787475586}, "rotation": {"w": 0.8220492601394653, "x": -1.52587890625e-05, "y": 0.5693598985671997, "z": -0.0001068115234375}, "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":true}}"}], "Id": "{aa6032b9-9c35-4481-acd9-03b97400b83a}", "Version": 93} \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e290531471..7a8dd6c767 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1193,7 +1193,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo getOverlays().deleteOverlay(getTabletHomeButtonID()); getOverlays().deleteOverlay(getTabletFrameID()); }); +#if defined(Q_OS_ANDROID) connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); +#else + connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRedirect); +#endif // We could clear ATP assets only when changing domains, but it's possible that the domain you are connected // to has gone down and switched to a new content set, so when you reconnect the cached ATP assets will no longer be valid. @@ -2313,6 +2317,35 @@ void Application::domainConnectionRefused(const QString& reasonMessage, int reas } } +void Application::domainConnectionRedirect(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) { + DomainHandler::ConnectionRefusedReason reasonCode = static_cast(reasonCodeInt); + auto addressManager = DependencyManager::get(); + + if (reasonCode == DomainHandler::ConnectionRefusedReason::TooManyUsers && !extraInfo.isEmpty()) { + addressManager->handleLookupString(extraInfo); + return; + } + + switch (reasonCode) { + case DomainHandler::ConnectionRefusedReason::ProtocolMismatch: + case DomainHandler::ConnectionRefusedReason::TooManyUsers: + case DomainHandler::ConnectionRefusedReason::Unknown: { + QString message = "Unable to connect to the location you are visiting.\n"; + message += reasonMessage; + //OffscreenUi::asyncWarning("", message); + addressManager->handleLookupString(REDIRECT_HIFI_ADDRESS); + getMyAvatar()->setWorldVelocity(glm::vec3(0.0f)); + // in (w, x, y, z) component-structure for the constructor + getMyAvatar()->setWorldOrientation(glm::quat(0.8775935173034668f, 0.0f, 0.4794054925441742f, 0.0f)); + break; + } + default: + // nothing to do. + break; + } +} + + QString Application::getUserAgent() { if (QThread::currentThread() != thread()) { QString userAgent; diff --git a/interface/src/Application.h b/interface/src/Application.h index ae7e686f35..5255bb7d63 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -471,6 +471,7 @@ private slots: void updateDisplayMode(); void setDisplayPlugin(DisplayPluginPointer newPlugin); void domainConnectionRefused(const QString& reasonMessage, int reason, const QString& extraInfo); + void domainConnectionRedirect(const QString& reasonMessage, int reason, const QString& extraInfo); void addAssetToWorldCheckModelSize(); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 00e552af89..0b69fda3fa 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -31,6 +31,7 @@ #include "udt/PacketHeaders.h" const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json"; +const QString REDIRECT_HIFI_ADDRESS = "file:///~/serverless/redirect.json"; const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; @@ -709,7 +710,6 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should // We use _newHostLookupPath to determine if the client has already stored its last address // before moving to a new host thanks to the information in the same lookup URL. - if (definitelyPathOnly || (!pathString.isEmpty() && pathString != _newHostLookupPath) || trigger == Back || trigger == Forward) { addCurrentAddressToHistory(trigger); diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 37b85a9acd..245517d8cd 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -23,6 +23,7 @@ #include "AccountManager.h" extern const QString DEFAULT_HIFI_ADDRESS; +extern const QString REDIRECT_HIFI_ADDRESS; const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost"; const QString INDEX_PATH = "/"; From 0da22db837d58c419ca0d58497c96164706b293c Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Tue, 28 Aug 2018 16:05:57 -0700 Subject: [PATCH 122/744] Picking pid values --- interface/src/Application.cpp | 2 +- interface/src/LODManager.cpp | 86 +++++++++++++------ interface/src/LODManager.h | 23 +++-- .../render/configSlider/RichSlider.qml | 3 +- scripts/developer/utilities/render/lod.qml | 52 ++++++----- 5 files changed, 112 insertions(+), 54 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 817c6b1e43..e7b17b3d69 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5987,7 +5987,7 @@ void Application::updateRenderArgs(float deltaTime) { _viewFrustum.calculate(); } appRenderArgs._renderArgs = RenderArgs(_gpuContext, lodManager->getOctreeSizeScale(), - lodManager->getBoundaryLevelAdjust(), lodManager->getSolidAngleHalfTan(), RenderArgs::DEFAULT_RENDER_MODE, + lodManager->getBoundaryLevelAdjust(), lodManager->getLODAngleHalfTan(), RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); appRenderArgs._renderArgs._scene = getMain3DScene(); diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 411e0156b5..7f99e74a2a 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -75,12 +75,8 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { return; } - auto Kp = _pid.x; - auto Ki = _pid.y; - auto Kd = _pid.z; - float oldOctreeSizeScale = _octreeSizeScale; - float oldSolidAngle = getSolidAngle(); + float oldSolidAngle = getLODAngleDeg(); float targetFPS = getLODDecreaseFPS(); float targetPeriod = 1.0f / targetFPS; @@ -89,29 +85,44 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { static uint64_t lastTime = usecTimestampNow(); uint64_t now = usecTimestampNow(); auto dt = (float) ((now - lastTime) / double(USECS_PER_MSEC)); - if (dt < targetPeriod * _pid.w) return; + if (dt < targetPeriod * _pidCoefs.w) return; float deltaFPS = currentFPS - getLODDecreaseFPS(); lastTime = now; - auto previous_error = 0.f; - auto integral = 0.f; + auto previous_error = _pidHistory.x; + auto previous_integral = _pidHistory.y; auto error = getLODDecreaseFPS() - currentFPS; - integral = integral + error * dt; + auto integral = previous_integral + error * dt; auto derivative = (error - previous_error) / dt; - auto output = Kp * error + Ki * integral + Kd * derivative; - previous_error = error; + _pidHistory.x = error; + _pidHistory.y = integral; + _pidHistory.z = derivative; + + auto Kp = _pidCoefs.x; + auto Ki = _pidCoefs.y; + auto Kd = _pidCoefs.z; + + _pidOutputs.x = Kp * error; + _pidOutputs.y = Ki * integral; + _pidOutputs.z = Kd * derivative; + + auto output = _pidOutputs.x + _pidOutputs.y + _pidOutputs.z; + + _pidOutputs.w = output; auto newSolidAngle = oldSolidAngle + output; - newSolidAngle = std::max( 0.5f, std::min(newSolidAngle, 90.f)); + setLODAngleDeg(newSolidAngle); - auto halTan = glm::tan(glm::radians(newSolidAngle * 0.5f)); + //newSolidAngle = std::max( 0.5f, std::min(newSolidAngle, 90.f)); - auto octreeSizeScale = TREE_SCALE * OCTREE_TO_MESH_RATIO / halTan; - _octreeSizeScale = octreeSizeScale; + //auto halTan = glm::tan(glm::radians(newSolidAngle * 0.5f)); + + //auto octreeSizeScale = TREE_SCALE * OCTREE_TO_MESH_RATIO / halTan; + // _octreeSizeScale = octreeSizeScale; /* if (currentFPS < getLODDecreaseFPS()) { if (now > _decreaseFPSExpiry) { @@ -363,36 +374,57 @@ float LODManager::getWorldDetailQuality() const { } -float LODManager::getSolidAngleHalfTan() const { +float LODManager::getLODAngleHalfTan() const { return getPerspectiveAccuracyAngleTan(_octreeSizeScale, _boundaryLevelAdjust); } - -float LODManager::getSolidAngle() const { - return glm::degrees(2.0 * atan(getSolidAngleHalfTan())); +float LODManager::getLODAngle() const { + return 2.0f * atan(getLODAngleHalfTan()); +} +float LODManager::getLODAngleDeg() const { + return glm::degrees(getLODAngle()); } +void LODManager::setLODAngleDeg(float lodAngle) { + auto newSolidAngle = std::max(0.5f, std::min(lodAngle, 90.f)); + auto halTan = glm::tan(glm::radians(newSolidAngle * 0.5f)); + auto octreeSizeScale = TREE_SCALE * OCTREE_TO_MESH_RATIO / halTan; + setOctreeSizeScale(octreeSizeScale); +} float LODManager::getPidKp() const { - return _pid.x; + return _pidCoefs.x; } float LODManager::getPidKi() const { - return _pid.y; + return _pidCoefs.y; } float LODManager::getPidKd() const { - return _pid.z; + return _pidCoefs.z; } float LODManager::getPidT() const { - return _pid.w; + return _pidCoefs.w; } void LODManager::setPidKp(float k) { - _pid.x = k; + _pidCoefs.x = k; } void LODManager::setPidKi(float k) { - _pid.y = k; + _pidCoefs.y = k; } void LODManager::setPidKd(float k) { - _pid.z = k; + _pidCoefs.z = k; } void LODManager::setPidT(float t) { - _pid.w = t; + _pidCoefs.w = t; } + +float LODManager::getPidOp() const { + return _pidOutputs.x; +} +float LODManager::getPidOi() const { + return _pidOutputs.y; +} +float LODManager::getPidOd() const { + return _pidOutputs.z; +} +float LODManager::getPidO() const { + return _pidOutputs.w; +} \ No newline at end of file diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index b2e324a820..e4ca9912fb 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -69,14 +69,17 @@ class LODManager : public QObject, public Dependency { Q_PROPERTY(float worldDetailQuality READ getWorldDetailQuality WRITE setWorldDetailQuality NOTIFY worldDetailQualityChanged) - Q_PROPERTY(float solidAngleHalfTan READ getSolidAngleHalfTan) - Q_PROPERTY(float solidAngle READ getSolidAngle) + Q_PROPERTY(float lodAngleDeg READ getLODAngleDeg WRITE setLODAngleDeg) Q_PROPERTY(float pidKp READ getPidKp WRITE setPidKp) Q_PROPERTY(float pidKi READ getPidKi WRITE setPidKi) Q_PROPERTY(float pidKd READ getPidKd WRITE setPidKd) Q_PROPERTY(float pidT READ getPidT WRITE setPidT) + Q_PROPERTY(float pidOp READ getPidOp) + Q_PROPERTY(float pidOi READ getPidOi) + Q_PROPERTY(float pidOd READ getPidOd) + Q_PROPERTY(float pidO READ getPidO) public: @@ -187,14 +190,17 @@ public: float getAverageRenderTime() const { return _avgRenderTime; }; float getMaxTheoreticalFPS() const { return (float)MSECS_PER_SECOND / _avgRenderTime; }; + float getLODLevel() const; void setLODLevel(float level); void setWorldDetailQuality(float quality); float getWorldDetailQuality() const; - float getSolidAngleHalfTan() const; - float getSolidAngle() const; + float getLODAngleDeg() const; + void setLODAngleDeg(float lodAngle); + float getLODAngleHalfTan() const; + float getLODAngle() const; float getPidKp() const; float getPidKi() const; @@ -205,6 +211,11 @@ public: void setPidKd(float k); void setPidT(float t); + float getPidOp() const; + float getPidOi() const; + float getPidOd() const; + float getPidO() const; + signals: /**jsdoc @@ -237,7 +248,9 @@ private: float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; - glm::vec4 _pid{ 0.06f, 0.005f, 0.01f, 4.0f }; + glm::vec4 _pidCoefs{ 0.1526f, 0.00000015f, 15.f, 4.0f }; + glm::vec4 _pidHistory{ 0.0f }; + glm::vec4 _pidOutputs{ 0.0f }; uint64_t _decreaseFPSExpiry { 0 }; uint64_t _increaseFPSExpiry { 0 }; diff --git a/scripts/developer/utilities/render/configSlider/RichSlider.qml b/scripts/developer/utilities/render/configSlider/RichSlider.qml index 1613cac1b4..01b14f3d48 100644 --- a/scripts/developer/utilities/render/configSlider/RichSlider.qml +++ b/scripts/developer/utilities/render/configSlider/RichSlider.qml @@ -29,6 +29,7 @@ Item { property var labelAreaWidthScale: 0.5 property bool integral: false + property var numDigits: 2 property var valueVarSetter: defaultSet property alias valueVar : sliderControl.value @@ -71,7 +72,7 @@ Item { HifiControls.Label { id: labelValue enabled: root.showValue - text: sliderControl.value.toFixed(root.integral ? 0 : 2) + text: sliderControl.value.toFixed(root.integral ? 0 : root.numDigits) anchors.right: labelControl.right anchors.rightMargin: 5 anchors.verticalCenter: root.verticalCenter diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 1ca9b68de2..90e7573fda 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -68,11 +68,11 @@ Item { RichSlider { visible: !LODManager.automaticLODAdjust showLabel: true - label: "LOD Level" - valueVar: LODManager["lodLevel"] - valueVarSetter: (function (v) { LODManager["lodLevel"] = v }) - max: 1.0 - min: 0.0 + label: "LOD Angle [deg]" + valueVar: LODManager["lodAngleDeg"] + valueVarSetter: (function (v) { LODManager["lodAngleDeg"] = v }) + max: 90.0 + min: 0.5 integral: false anchors.left: parent.left @@ -85,9 +85,10 @@ Item { label: "LOD PID Kp" valueVar: LODManager["pidKp"] valueVarSetter: (function (v) { LODManager["pidKp"] = v }) - max: 1.0 + max: 0.2 min: 0.0 integral: false + numDigits: 3 anchors.left: parent.left anchors.right: parent.right @@ -98,9 +99,10 @@ Item { label: "LOD PID Ki" valueVar: LODManager["pidKi"] valueVarSetter: (function (v) { LODManager["pidKi"] = v }) - max: 0.02 + max: 0.000005 min: 0.0 integral: false + numDigits: 8 anchors.left: parent.left anchors.right: parent.right @@ -111,9 +113,10 @@ Item { label: "LOD PID Kd" valueVar: LODManager["pidKd"] valueVarSetter: (function (v) { LODManager["pidKd"] = v }) - max: 0.1 + max: 10.0 min: 0.0 integral: false + numDigits: 3 anchors.left: parent.left anchors.right: parent.right @@ -124,9 +127,9 @@ Item { label: "LOD PID Num T" valueVar: LODManager["pidT"] valueVarSetter: (function (v) { LODManager["pidT"] = v }) - max: 5.0 + max: 10.0 min: 0.0 - integral: false + integral: true anchors.left: parent.left anchors.right: parent.right @@ -199,34 +202,43 @@ Item { ] } PlotPerf { - title: "LOD" + title: "LOD Angle" height: parent.evalEvenHeight() object: LODManager - valueScale: 0.1 - valueUnit: "" + valueScale: 1.0 + valueUnit: "deg" plots: [ { - prop: "lodLevel", - label: "LOD", + prop: "lodAngleDeg", + label: "LOD Angle", color: "#9999FF" } ] } PlotPerf { - title: "Solid Angle" + title: "PID Output" height: parent.evalEvenHeight() object: LODManager valueScale: 1.0 valueUnit: "deg" - //valueNumDigits: 0 plots: [ { - prop: "solidAngle", - label: "Solid Angle", + prop: "pidOp", + label: "Op", color: "#9999FF" + }, + { + prop: "pidOi", + label: "Oi", + color: "#FFFFFF" + }, + { + prop: "pidOd", + label: "Od", + color: "#FF6666" } ] - } + } Separator { id: bottomLine } From 4b7f6a346fecaf0b8b9e508254dfff805498227f Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 28 Aug 2018 16:21:47 -0700 Subject: [PATCH 123/744] Use AvatarData::getClientGlobalPosition() for position in priority queue It looks like it's the same as world position with the avatar mixer. Also use one time stamp for current time in priority queue; use std::chrono for some other timestamps in hope that it's faster. Fix priority-code logic bug. --- .../src/avatars/AvatarMixerSlave.cpp | 26 +++++++++---------- libraries/shared/src/PrioritySortUtil.h | 12 ++++++--- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 50ddcec92b..f0c291b2c2 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -33,6 +34,8 @@ #include "AvatarMixer.h" #include "AvatarMixerClientData.h" +namespace chrono = std::chrono; + void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { _begin = begin; _end = end; @@ -301,9 +304,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) }; // Temporary info about the avatars we're sending: std::vector avatarsToSort; - //std::unordered_map avatarDataToNodes; - //std::unordered_map avatarDataToNodesShared; - //std::unordered_map avatarEncodeTimes; + avatarsToSort.reserve(_end - _begin); std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { Node* otherNodeRaw = otherNode.data(); // make sure this is an agent that we have avatar data for before considering it for inclusion @@ -313,9 +314,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarData* otherAvatar = otherNodeData->getAvatarSharedPointer().get(); - //avatarsToSort.push_back(otherAvatar); - //avatarDataToNodes[otherAvatar] = otherNode.data(); - //avatarDataToNodesShared[otherAvatar] = otherNode; auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(otherAvatar->getSessionUUID()); avatarsToSort.emplace_back(AvatarSortData(otherNode, otherAvatar, lastEncodeTime)); } @@ -326,7 +324,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) SortableAvatar() = delete; SortableAvatar(const AvatarData* avatar, const Node* avatarNode, uint64_t lastEncodeTime) : _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {} - glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } + glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); } float getRadius() const override { glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale()); return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); @@ -452,7 +450,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame; - quint64 startAvatarDataPacking = usecTimestampNow(); + auto startAvatarDataPacking = chrono::high_resolution_clock::now(); ++numOtherAvatars; @@ -504,12 +502,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray bool dropFaceTracking = false; - quint64 start = usecTimestampNow(); + auto startSerialize = chrono::high_resolution_clock::now(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - quint64 end = usecTimestampNow(); - _stats.toByteArrayElapsedTime += (end - start); + auto endSerialize = chrono::high_resolution_clock::now(); + _stats.toByteArrayElapsedTime += + (quint64) chrono::duration_cast(endSerialize - startSerialize).count(); static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; if (bytes.size() > maxAvatarDataBytes) { @@ -558,8 +557,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) avatarPacketList->endSegment(); - quint64 endAvatarDataPacking = usecTimestampNow(); - _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + auto endAvatarDataPacking = chrono::high_resolution_clock::now(); + _stats.avatarDataPackingElapsedTime += + (quint64) chrono::duration_cast(endAvatarDataPacking - startAvatarDataPacking).count(); // use helper to add any changed traits to our packet list traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 34ec074d45..a2be5fadcd 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -87,6 +87,7 @@ namespace PrioritySortUtil { PriorityQueue(const ConicalViewFrustums& views) : _views(views) { } PriorityQueue(const ConicalViewFrustums& views, float angularWeight, float centerWeight, float ageWeight) : _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) + , _usecCurrentTime(usecTimestampNow()) { } void setViews(const ConicalViewFrustums& views) { _views = views; } @@ -95,6 +96,7 @@ namespace PrioritySortUtil { _angularWeight = angularWeight; _centerWeight = centerWeight; _ageWeight = ageWeight; + _usecCurrentTime = usecTimestampNow(); } size_t size() const { return _queue.size(); } @@ -129,16 +131,17 @@ namespace PrioritySortUtil { glm::vec3 offset = position - view.getPosition(); float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance) - float radius = glm::min(thing.getRadius(), MIN_RADIUS); + float radius = glm::max(thing.getRadius(), MIN_RADIUS); + // Other item's angle from view centre: float cosineAngle = (glm::dot(offset, view.getDirection()) / distance); - float age = (float)(usecTimestampNow() - thing.getTimestamp()); + float age = (float)(_usecCurrentTime - thing.getTimestamp()); - // we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward + // we modulate "age" drift rate by the cosineAngle term to make peripheral objects sort forward // at a reduced rate but we don't want the "age" term to go zero or negative so we clamp it const float MIN_COSINE_ANGLE_FACTOR = 0.1f; float cosineAngleFactor = glm::max(cosineAngle, MIN_COSINE_ANGLE_FACTOR); - float priority = _angularWeight * glm::max(radius, MIN_RADIUS) / distance + float priority = _angularWeight * radius / distance + _centerWeight * cosineAngle + _ageWeight * cosineAngleFactor * age; @@ -157,6 +160,7 @@ namespace PrioritySortUtil { float _angularWeight { DEFAULT_ANGULAR_COEF }; float _centerWeight { DEFAULT_CENTER_COEF }; float _ageWeight { DEFAULT_AGE_COEF }; + quint64 _usecCurrentTime { 0 }; }; } // namespace PrioritySortUtil From 223f86108f62b6b89a53c2542f2c26a7cd3cc957 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 28 Aug 2018 17:34:48 -0700 Subject: [PATCH 124/744] adding debugging stuff --- libraries/networking/src/AddressManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 0b69fda3fa..732a2d7963 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -48,6 +48,8 @@ QString AddressManager::getProtocol() const { QUrl AddressManager::currentAddress(bool domainOnly) const { QUrl hifiURL = _domainURL; + auto urlStr = hifiURL.toString().toStdString(); + if (!domainOnly && hifiURL.scheme() == URL_SCHEME_HIFI) { hifiURL.setPath(currentPath()); } From 37b4cf6223c2a8ce9cc20b99a4f014d53d8480dc Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Tue, 28 Aug 2018 17:39:35 -0700 Subject: [PATCH 125/744] Picking pid values --- interface/src/Application.cpp | 3 ++- interface/src/LODManager.cpp | 12 +++++---- interface/src/LODManager.h | 8 ++++-- .../utilities/lib/plotperf/PlotPerf.qml | 27 ++++++++++++++++++- scripts/developer/utilities/render/lod.qml | 2 +- 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e7b17b3d69..fc62b01ca6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5028,8 +5028,9 @@ void Application::updateLOD(float deltaTime) const { float presentTime = getActiveDisplayPlugin()->getAveragePresentTime(); float engineRunTime = (float)(_renderEngine->getConfiguration().get()->getCPURunTime()); float gpuTime = getGPUContext()->getFrameTimerGPUAverage(); + float batchTime = getGPUContext()->getFrameTimerBatchAverage(); auto lodManager = DependencyManager::get(); - lodManager->setRenderTimes(presentTime, engineRunTime, gpuTime); + lodManager->setRenderTimes(presentTime, engineRunTime, batchTime, gpuTime); lodManager->autoAdjustLOD(deltaTime); } else { DependencyManager::get()->resetLODAdjust(); diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 7f99e74a2a..a32f3472ab 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -58,18 +58,21 @@ const uint64_t LOD_AUTO_ADJUST_PERIOD = 4 * (uint64_t)(LOD_ADJUST_RUNNING_AVG_TI const float LOD_AUTO_ADJUST_DECREMENT_FACTOR = 0.8f; const float LOD_AUTO_ADJUST_INCREMENT_FACTOR = 1.2f; -void LODManager::setRenderTimes(float presentTime, float engineRunTime, float gpuTime) { +void LODManager::setRenderTimes(float presentTime, float engineRunTime, float batchTime, float gpuTime) { _presentTime = presentTime; _engineRunTime = engineRunTime; + _batchTime = batchTime; _gpuTime = gpuTime; } void LODManager::autoAdjustLOD(float realTimeDelta) { + // float maxRenderTime = glm::max(glm::max(_presentTime, _engineRunTime), _gpuTime); float maxRenderTime = glm::max(glm::max(_presentTime, _engineRunTime), _gpuTime); // compute time-weighted running average maxRenderTime // Note: we MUST clamp the blend to 1.0 for stability float blend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * maxRenderTime; // msec + // _avgRenderTime = maxRenderTime; if (!_automaticLODAdjust || _avgRenderTime == 0.0f) { // early exit return; @@ -78,7 +81,7 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { float oldOctreeSizeScale = _octreeSizeScale; float oldSolidAngle = getLODAngleDeg(); - float targetFPS = getLODDecreaseFPS(); + float targetFPS = 0.5 * (getLODDecreaseFPS() + getLODIncreaseFPS()); float targetPeriod = 1.0f / targetFPS; float currentFPS = (float)MSECS_PER_SECOND / _avgRenderTime; @@ -87,13 +90,12 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { auto dt = (float) ((now - lastTime) / double(USECS_PER_MSEC)); if (dt < targetPeriod * _pidCoefs.w) return; - float deltaFPS = currentFPS - getLODDecreaseFPS(); - lastTime = now; auto previous_error = _pidHistory.x; auto previous_integral = _pidHistory.y; - auto error = getLODDecreaseFPS() - currentFPS; + auto error = (targetFPS - currentFPS) / targetFPS; + error = glm::clamp(error, -1.0f, 1.0f); auto integral = previous_integral + error * dt; auto derivative = (error - previous_error) / dt; diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index e4ca9912fb..74075f2400 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -59,6 +59,7 @@ class LODManager : public QObject, public Dependency { Q_PROPERTY(float presentTime READ getPresentTime) Q_PROPERTY(float engineRunTime READ getEngineRunTime) + Q_PROPERTY(float batchTime READ getBatchTime) Q_PROPERTY(float gpuTime READ getGPUTime) Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) Q_PROPERTY(float fps READ getMaxTheoreticalFPS) @@ -178,10 +179,11 @@ public: float getPresentTime() const { return _presentTime; } float getEngineRunTime() const { return _engineRunTime; } + float getBatchTime() const { return _batchTime; } float getGPUTime() const { return _gpuTime; } static bool shouldRender(const RenderArgs* args, const AABox& bounds); - void setRenderTimes(float presentTime, float engineRunTime, float gpuTime); + void setRenderTimes(float presentTime, float engineRunTime, float batchTime, float gpuTime); void autoAdjustLOD(float realTimeDelta); void loadSettings(); @@ -240,7 +242,9 @@ private: bool _automaticLODAdjust = true; float _presentTime { 0.0f }; // msec float _engineRunTime { 0.0f }; // msec + float _batchTime{ 0.0f }; // msec float _gpuTime { 0.0f }; // msec + float _avgRenderTime { 0.0f }; // msec float _desktopMaxRenderTime { DEFAULT_DESKTOP_MAX_RENDER_TIME }; float _hmdMaxRenderTime { DEFAULT_HMD_MAX_RENDER_TIME }; @@ -248,7 +252,7 @@ private: float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; - glm::vec4 _pidCoefs{ 0.1526f, 0.00000015f, 15.f, 4.0f }; + glm::vec4 _pidCoefs{ 4.0f, 0.0000000f, 0.f, 4.0f }; glm::vec4 _pidHistory{ 0.0f }; glm::vec4 _pidOutputs{ 0.0f }; diff --git a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml index 916c9cae55..99ff5f712e 100644 --- a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml +++ b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml @@ -48,6 +48,7 @@ Item { property var valueMax : 1 + property var valueMin : 0 property var _values property var tick : 0 @@ -90,6 +91,11 @@ Item { _values[i].valueMax *= 0.25 // Fast reduce the max value as we click } } + function resetMin() { + for (var i = 0; i < _values.length; i++) { + _values[i].valueMin *= 0.25 // Fast reduce the min value as we click + } + } function pullFreshValues() { // Wait until values are created to begin pulling @@ -99,6 +105,7 @@ Item { tick++; var currentValueMax = 0 + var currentValueMin = 0 for (var i = 0; i < _values.length; i++) { var currentVal = (+_values[i].object[_values[i].value]) * _values[i].scale; @@ -112,26 +119,44 @@ Item { _values[i].valueMax *= 0.99 _values[i].numSamplesConstantMax = 0 } + if (lostValue <= _values[i].valueMin) { + _values[i].valueMin *= 0.99 + _values[i].numSamplesConstantMin = 0 + } } if (_values[i].valueMax < currentVal) { _values[i].valueMax = currentVal; _values[i].numSamplesConstantMax = 0 } + if (_values[i].valueMin < currentVal) { + _values[i].valueMin = currentVal; + _values[i].numSamplesConstantMin = 0 + } if (_values[i].numSamplesConstantMax > VALUE_HISTORY_SIZE) { _values[i].numSamplesConstantMax = 0 _values[i].valueMax *= 0.95 // lower slowly the current max if no new above max since a while } - + if (_values[i].numSamplesConstantMin > VALUE_HISTORY_SIZE) { + _values[i].numSamplesConstantMin = 0 + _values[i].valueMin *= 0.95 // lower slowly the current min if no new above min since a while + } + if (currentValueMax < _values[i].valueMax) { currentValueMax = _values[i].valueMax } + if (currentValueMin < _values[i].valueMin) { + currentValueMin = _values[i].valueMin + } } if ((valueMax < currentValueMax) || (tick % VALUE_HISTORY_SIZE == 0)) { valueMax = currentValueMax; } + if ((valueMin < currentValueMin) || (tick % VALUE_HISTORY_SIZE == 0)) { + valueMin = currentValueMin; + } mycanvas.requestPaint() } diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 90e7573fda..fe284fec93 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -85,7 +85,7 @@ Item { label: "LOD PID Kp" valueVar: LODManager["pidKp"] valueVarSetter: (function (v) { LODManager["pidKp"] = v }) - max: 0.2 + max: 2.0 min: 0.0 integral: false numDigits: 3 From 442644381ad20d829459fa281b7fc5c67e959db4 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 28 Aug 2018 20:16:55 -0700 Subject: [PATCH 126/744] adding other connection refused suppressions --- interface/src/ConnectionMonitor.cpp | 4 ++-- scripts/system/notifications.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index 8deddbda82..7d535d9eaf 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -38,7 +38,7 @@ void ConnectionMonitor::init() { connect(&_timer, &QTimer::timeout, this, []() { qDebug() << "ConnectionMonitor: Showing connection failure window"; - DependencyManager::get()->setDomainConnectionFailureVisibility(true); + //DependencyManager::get()->setDomainConnectionFailureVisibility(true); }); } @@ -48,5 +48,5 @@ void ConnectionMonitor::startTimer() { void ConnectionMonitor::stopTimer() { _timer.stop(); - DependencyManager::get()->setDomainConnectionFailureVisibility(false); + //DependencyManager::get()->setDomainConnectionFailureVisibility(false); } diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 0778e2a44b..13d500b909 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -624,7 +624,7 @@ Controller.keyReleaseEvent.connect(keyReleaseEvent); Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); - Window.domainConnectionRefused.connect(onDomainConnectionRefused); + //Window.domainConnectionRefused.connect(onDomainConnectionRefused); Window.stillSnapshotTaken.connect(onSnapshotTaken); Window.snapshot360Taken.connect(onSnapshotTaken); Window.processingGifStarted.connect(processingGif); From b338c9f6fa42f354c30294b6f499d87bfaa992b2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Aug 2018 22:10:24 +1200 Subject: [PATCH 127/744] Updated tiny table model and layout --- scripts/system/assets/models/tinyTablet.fbx | Bin 411712 -> 476000 bytes scripts/system/html/css/miniTablet.css | 54 ++++++++++---------- scripts/system/html/miniTablet.html | 4 +- scripts/system/miniTablet.js | 6 +-- 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/scripts/system/assets/models/tinyTablet.fbx b/scripts/system/assets/models/tinyTablet.fbx index cdcdf8af3461946f9050f07f897312fb77f5ef38..4ad5d8760fcb2828ff0011eb2925066e9cd9d6ae 100644 GIT binary patch literal 476000 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

)SB@xlHcG4b5zVY7|ncEbVzqVDS*@a(e7 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

44WJ$%?2%lm+N=eq@)bJhVRr6rG1Jx-?X zuTICcep-NH35;qmHrM$J$Ko$>ry?R_R2J>%XZgg>iu1c+Jv+~4y60Ecx-{Hv(;HoK zU$7Hbf28vIId>u*hAe^0{<+beGLWXJ4#Xz2Xu7mw1$a;Kwv`<3sWHdb?m3R?ZnFy& zuk;$!fo<=X{eMA?9BF9k+9)@g;89){Xj;zFh!}yW>3#vpVSP(X`HTJhb=ezu5qdsw!+0D=Arzh9tJgwEqK7Sd6lJbshF$d*unlCj2Mroc8C+UYgMW@V$5gp^K9&Y2R{(ga zlYu3EVRqTw7a@(2`!ZchNS{%UOv)u7Cx43O2`OC&`w~`c_iyij&R|dD6P70a-uVsk&I6iO{n*79y@1MpYfx?fS$;N`{-2aZtF5tK{{1h?8qa}qBG!{=O zyx+i`l1CbD_&woyNtszuj+!LFYwTN0)**N~gs39`(H333Y4Kfx;bYdTHa-pYp7b5>9hRRNZ+3>>HEla%&nT3DgMO(*Dds zoN%YIxMoi&9cgn#hZ~C8raSJNYgclzQ&K!ut@YV%V#%4;7jorw&|(QvE2>XSrFOkT zYun`*EXVLrUbl_F6#xr7;(W9drYOpXQZ}7l5l)tigwyy_FVeCSRje~krPlS7T#3)= zRE3NLX&E$prpD*N;$xHXIrz0@+38UexjjiDT2DjJT4g-waD`KJH8`bc{JTeeQ2f^O z_-8P4bK2$VbM7%u)ACEvCtypXdoy;zx%s9!_wgv`r7E7{9PU&fBlPN8JCx*;RCSH2+xEkXrD%Ol?$V{^a*WBFE->@(#fZo`8d?**{ zNWnWDiX1kImp*Nh6D?8hl}_4ls^#(~#c zCXz$=eLvoMLShaG0;#fA9+T7Ss6vCI`)nouthquHwxMqiG+G34Ok#XwORopNA2QEIF^}j_}mz zR->WL`WeDPFzpZw0j~0;t0R(Dbl3>TE&wO4 zWJRCoS)Z`n4JNPZ+Tz-^boJh-y18 z{U0lc;pVQ^%%I_@e$6qPz(TK4r9}^yxL`jnsK#d;7)q7tR6($-Lvbj?m^8_;?VNCk za&?L>M`&VZ)Lsv*uEeWpTf;idH?+Xr0YSeZ{mYG~I~l*kbHf%9EUPg)7HvzeNM(d# zkPSX}Vd@ZP#laVH$Ko<%0Hb6<_H?`gf{2bD&sKUKtW~%0`ge7_k9clF=6!j|KkHAQ z2mQu;Hvg7biI!;?awUB9xG4FPk&oCW+DiJD$frC+Zp(zkGE$ul$Pv0wgS6SCDdYwv zz24>DK;O|)|EN2{zJ-dXCW;5^oaC=B_-`^wyDj?tZYXFU{y2LL9HPa>R!I(@oE~ zVWLyLkV7dMy&4KIJlwn^ zxvsHo_qW%y@mBKBliM7(Nd4KLc6u(GdM_4_`YVlDMgNo{& zSYFr3vfQG1Y?8>?ZpNok1FQ#u7PKdy7#+Vj)JOD12kWj+IO&~>DhdCcmKp$v`B|we zn`IMj?;=vmJP?dbSX;JqS-$m|J-C;vBL-Yk1+2hefMUu`9H+ov;}M8~4qp#qi&1p_ zX8M@jc1v!Enn8qnBKCRP};+OIkDHZbnLo3;C8P=C6gdMEAtl^ z$LgfUS%6d54=nHkYM0+>{l)v_$~y2}bSCPQRN6&c zpDq?Vz&vV%r#7G|uB0p6K`VjOZ)@0@Xdi3`E!Y2fvhD7`XCG@;EUqn(7>UNb77>Oe z5SMT=nC<1J#5bU1EXiG#SNrV|HVyR=MT4yG0*4E19i^ZuWLL=1KjtX4-@=|^#8Zae zpcq?rxb@cgXKxbc-MaO5mdU3{`#m$(0&+Io$U%89`#g7;+?`Ih)cME0tXNjD-w;R? z^B{FoGWT}U2DLM*_Nj;pLoS-hJVfnoU`lHmZ*t0M118~Nsu@jjBYGmG@sVF%ieW7f z)VHVf(E*AoEokhXXskbdD&CD&h!W;TcC<|vw>umNZvF6e4@S2=%nU}|`xWj+WgQh> zOzP_UsGBwbdN1nKJgX=6NnUuh-(Fj_@Q-hA?CkARHE-ag8lFc=n-&r^!ubwh+KNgE zG#Z|VuEHq#Zt7U& z9hm=)vwC(GR~;;yh~DUr<*2a_X|G0VsYyIF>RiT1{m=`~a8xBvqAK}^l*}-F z285NWmLL&%FXB`G6Cb9VUKEWSTYS(z60hq$P!2ElJtsrSh;lwvR-RHM^@R)P7;A49 zi8a52+<Tp{-l*q%RJ$ak>M~;@K&qtsy z5Y-^g!yV(TS=sjvSYeiOHp8-&&EqIr_wt`8sN*qj8j{hU@PZY%BPfg#KtetimKv_S zUCp9=5Gvd+b9P6ab73#UXlvlZs)J_wEeM5j!rh3vpo4i9PWzxF8N;oYoARa4z)JU9 zj>_RED5#`(0ud|;P$o2~_W_pP92Bt>-`6o`f7dICUJjAOP|gCjshLsa-ximRT0>i@ z{<4qiiYGc^mkZxwtkwEzE0;FMRLq;%n07A!A9*YQg;Ki?hlVtfDe=0*iPZuA=7%*c{a#*<};Yq)6_4e zK+law>9rUzFg&}5kC6i#$nl#(kAA9|!R0C>#6KXwymijJN>9o}TJ&bW} z!Zh!y<`8f~@sp1zz0Vl3oSBiHt{Ze0-a%1+@yM)LC)$e-8$Zln!8Airx*IcWxl(Mm zS@mMRJxi3Gvjr1M(;O<8yZ`5pN2F?2G8MX?!KV2bN@%5Rr2_ zOX$M2@7@6_(ftkwI;g^&UJ?m@D1A%} z)-s;bB;B^92w`5_4g|q%ST=e*_`|tZ)@$Bi)#q3OOlo`s?p)403{sn9Xa z&oyfF8UrwtJW##paqi^C6saIY1^tAmrk8`~S0V3}Kd>Nn1wx0_45j|7R5OHr=ux@- zsiC)YQ3R+}sr)|}-gjmOwJno7+((GSr73vDzViA$I-PxDYn1TtAWoSwW~slHpS zMnsE3CAZ#B*OwiSx;H}eR`h|JaNIYKGi+A3EmvSfTY{3e8&$T8jr1_=ap`8ZJJ|w> z#yv>9iv~6Aqxzn3xtivfMjwVBs~y^Cti|&|S6OjTOT2ohD)};npryC1P;>KUOM`>d zS(wQF6cg{$jtNB_URvqKJy`t5od*Xdx$H4;F)vbjHcMw`XM|2{^cCG(A{6f3;X{Rz zs|fVGuHd33;8wESk~BJmPkQbpmN0(kP1$v*(cREkj|grztI&(;>9BlaiRu=}$TIqL2% z^>R~;Nwd}*3SPZRDF2N2??x=wuLB{r7YtD=k##(JQLrdjmb1>Xv&r*UY$|Sn78wNo zfyl|-?q!z`E*MYA7V@O$-H&dSE_^6%LR<8}!~OhrKV#3L#HT^+DCMZHI*P_(Pa_09{T_I|^VJ)q&l&?{%A%~swGqbyFH_QTq-C@Tj0^;mEBWST$K9eI3 zyRnuB2NsGiRuQ_*;zD7st~U;Nz-~Y?oGBJnDU|j+{e|Om=S1&)+t`0&uGL@2<*Gia z!^a7{-mfF>f3c|pO155XnTTenor_A@4o`agOVnG0vSi)?0MyU08wdyc)@BREBa~R| zYf%_5w-B-STJT6*q#?26jw`(V_+52I2|9MXQgZq{K|fJ4gKOsu-cQ9$*JR)WFnA-0 zf{|s!FYl=SUt=8)aEd7~#bVADu%5D0n+`8^d$XuX7O1IIISVEKB^vZR3`BA~(P3e` znS6^K3dmM6BqgDI$yJqUG8(v|leHep>3@kqO++W@>nnax zd4FfUoP5X)lD>?g7IpeUp8e$Ke`|IqnJT%@LEkBta(8kcR?pp~lGBalxn;n+CQhyD zYvHRce+;~*F8sYA_D&+Z+gP%qLbqg-mQi?IXd1mi9x;sTfrat|QM=^pKl1|TOy|9v z6v<2wnlAy1Pnh|-*5aqW!d5xY~6T+$i4@J?hDq|#Mu9R9II_=Kw59`4fS@``>~MqF}q|PnhynWEmS#+ zw3HbhR0%%X69Id_6OyJ`BCTtTe)x-YHM^Ogc{`1qDt(dYEO z>p!7^>#xp)abKOKZ!AQk;ujz7(}>9`=qUP+dM9ykHF6wx5VFfIX=rBg3RyWx3J5h4 z=I_pGk}VUMNwZ!^dc;&n%%gA0Oo_&t>1fug^5ziRQmj0gop`$eK0h^Igp~6Ddep!M z4>{`LjE3*avs}T+A(76KjzmmyL&}3hfZK9|?IW6M&Y0|6SpTeV)ulkZm!x1*YWH<{ zliP&f_(BF62OUULXZWs8r)L}3E=<-Q_BE`Ia}A)%z&0=zMTQIUbpKWK0+zMLbdX(W zfa?iui6Z?VO9e1o%HUVgS(eX!cXw-UCM8KF9+M+#zTc=US=%>)GMUc^6^535;3FA` zr{oRU!8o}#w5N$>!)C&&{Nk!R)hs0E`t=$^$%M8Vk1-o@7anI9)t<8JYX3w(rO~KR zdXUMNTx-fs8k;m!a{jzxXb0?35fL{e`p3m}CBVe*-0CKLU55t>iO}j4JuK)V*El!| zvX!1U-@frVB~`j{iS0{<+E?jenlh=*ed6}H;S%s=)@2e_^r7(1Zni&6*mx?js`apE zvFFF*QYdsuW0>LSo9}7L*W-E5RWY=sc(?Q{wk3>ehJ~-~{YYbHLfB8}kUHGLiHW(Q zh`_{u9mIa*9vq)itA4O?!Ww-MzC}OC@$ts$*QuO##4yNW<xE-^l`7yFpaSN!UdV3o-nX`tuoLYpEGPg- zbr$Y?PDG|pj1Rt@4yACN>L?{0sg6f<|Wx0OAD0oyLzwTG8L zZS6KQ9jT#KB=33uq`=eHlZ;Mb|2p7M=gN>_+~FZ&+-m0@D3oBG2#O-uUM6 zqOMOOFxtab1nI3KN=tjGPdJP5d2ILi$z>N%v*XLkq)Gz1=?>FZ4avE;s;_)wI9|~=J8v2y$3w|}?O5;1;h|wPk z(A{2qQ&k-|xE)$FCWVlsg=DZ9zUVZ3rkLrOj^Dm=Cd+HZ`Iu(Ljr+s=SOkpvCgewy zBfcxm+6NLJUkhvN@;tl{_7XjsSteY-l0vi}WomcU6MV^~7wK3EFEU1<>!(8N8XFZQ zMpt*GYiq^=YB#EpKlc$PWG3Z(0beZZ0$awezE4tTLIBkA?L+xFgCL;6!_Tcp92DSQ zSrg91P80IJ>yMCq%Bs=h@!pkDkB{7AttJmIGk{5EZDxZ>`#X~^3QpGMkkpG$m)H4juA@^}Ts?jQhZ)@R+@L15_?abv#+* zpP<&wV`3ZM8f9<~h95x3{!W%1QM_^-{TbSU*Zv{D0 zT4`4n?Vl+=BjMnDNbAlK5!dWEJxxSDhx+w#%Rs8DnD3C+ZrP)D)(!mBaGJd$J`#Wb zyDi6kRxG#U;td8kIm3AL)xP7oK0}h>l>$ZmC94oRBdT;!PJp-e42wD#e=REAJU?BN znnu$Ip(YLzN9LtoZ1N@yh*}K5wRBor>(J&pZ-aS7gm;o>=EG%4i(S;|%0>&UxWCXx_1GdgGXS^)}gG zKNFxnVZhFT&2{e~lTOCbzpU6|IF;3(mxsBzAu1hYn38pT70%mCSU8;sU>zOujB7OK zD}NTNdUv#|Z^_EseHXZrA@O)uT(LEQWO}Q3(1Q?A?%mRitU@NAoM@WQ%vSXZC)};L z`c3bR+68@U_8eJn&{UIfp%OVv`)I(-6TjJe7v`+A;a6!Q!7J>?@bV?GKzNkmC-pX& z?p=BkHuR$Ds67=!INz_ETMJNI;^Fnw&m03((mbCoRS7hiUip-atIm;MHRau zE+V!yHF2q_sqmZez`vihd!LB77pT4qdOtJ8bm9K1jADicM1NARLqshO`WBhS3Kfwo zUT!)=yhQM{QgCx`S%ede$%(C%r{-QrFwVS@VZ_jfnCb+oS)!XizSZW=A^#H;l(pbj zowldtb<&b)?1~I;jFCwk2t_yHI!p_q5HzAN>~ozWA~FLmCNE@HS`jI*yJ$gZQ3Z1v z+?E8A7GIRf*~o)wdCxf214``*?xIdl_HotX3myx4iS?mdE?#zqTc^TtaMDZNfn{9? zP05jvR3XDqSuP&j|&d7lP|tvVjWNqrCHq z=(M>3#F>9YlHQ0t&ve0MQxAzd42<}5TrVnK)FQ3b3hX$oY-n_Wxr;t5=ma}m*9%O< zYh#QGDJd23Ol*VhV00_TVC{nMi0%HDh3aLhtQk&8d9$4RU{=3Sa^hFx@z6|jF`>dR z#&-;3%~V)nW56aC{DZEosw(Y^yUT%T?tS8+K=cu@p=v+QbFbnjhm}yZPrUPwlm1KZ zf?coF26flCojXOgFd`8Zo<=>bVTB84otlPMQwy-z{aKX!4QgU9dg`YFIe8u`3>)|z z`$%)nRE&%?tR6q6H9ObgEv`LhdXluoe*X#8z}M}$a}-&l{BDu}?kW3>h5An4g6MQc8jS z%F<9fllP3LZ(Cq0q=3{Qr2ubEq5pBtZ^5>Px$#dLKk4*EqR@DDeAG|nCNQyis^K!A ztU>Usu%z@_Bd~9ya=_j@R$ooQw!uneO3IWo`Tr6rw4FxcL(SL4W#p(d$1m?GsH?|w zH^%1i1wFa4>D(v#YIOD33)1FY=wV%+jbf+HsXmw~uk?1z6r=oT0!i~^)rY)@=&0p# zJ90vrW-IX9GwB>_u^|!?B>YojJn51*^8Hsg76zbyluJbeX)|g~8>>DCRw%X8tZMJe zINOYMtNE2C?RS)Z5fm%xs&(f3bh7CCueyo1&I`hm;TlY)T`wDM0UoWOT8rBdzoYCq zX67Rk->Rk6Z?Sr$G;w37>JGTLtgKU|GEvbkv~Ydjj2fAaYF{(w`G!_e`|<2+4g(}3 z_yczYCfBrY)RB%z^e&t=i$FvpP;|X#e$udbpkmhob&>P%*Prn|>Crw$0!j^S(0F^@}c>b*k^cCyp4r=L2V=qRrpxjKStp46!lu2r+W zvb`jfi0J8e32yieYO1Ir!`-jMU(+Jdq5z#2iY)qVUXS+nr2Q%;o8?2r#VB-}O4vmh z`&D-OJ`rc}T#j81WZ12odM-O5e*VHOP$365C+!}2AzqvzV6YN+QjO*H=M@Q-ivjCwZ>YJxO# zaZ9_V6ep-mH8{~XE|S`ByKlN#Gou4~V{OxM&IipS3ZV7$C*CSXoxg_x6^bEqGL3oi zu6^KMS*K++J1BQRz(W%Wo>$Sp#uv~KkltoD8aQAV@v7m}cNA6m4kV30V!%}3@h7y` zD2yqtRMKohb)e>;*{pb=gnC8L92-+C7aN%QJ@PuTbA0}jX={%*avq18!%~`JGvgAF z&3qa#lGsGKLHukYg~~PZcK4hj;n%52fK|0@q5j_M8c;Loz#q+@uXp!mEnJyD4D)_A zCr2@fhyH?->n=lXPlA7NXm zRRTxWy4{;H_}k)shMgZ5J?rBa?16c^CyyWJsUbFY$WsK->}5Q+O=3+`Udwn>$( zAdATfAtN#N?o?SyrJ`tsr&C?sh>bp>!rWh9zxd#H9J*U{=fW6>guX-hU%6etsucD- zY_Z9SAZu=v8|t&qdi@*8=Bd5BW8^%u41wB*amGb^TS{@ij6_J#So^%=?82gTl5(?lhH zRdp^&xgs&32kUR9ZGBKUBh4A{^9Ds}t>=xYqo^vWwn^@f1k|Zdh1U~eQcmt8@;DPI z56(+hA?oTD^=V)@?3ZJjph%ajP9}E>N!l@BSU~&mJ5iO%zljHZgVe^OxxJ+3yN0+g z-*zO=y?7q`)87Bc>eBi{41_!F0MrhGnt+Xv6Z_o;)fl_SK1rn)ZDYhm!n6b2kkO0gqWS%H-b#D@6l}81KqT1_x3Gw+`5HW zftZW1b%xLVuL&Wwv@m7S4NAy6LR4oU{g|LKnI6AM8^oJnfi8UGj;N`Kovg^vQVP9@ zG!Ff9LoRVj>7|8r)r?5{$Cz6mqA+Qvh;cTy--Wr4<&t03tBwmRTDK(k=p^-PxrX2; z(08tzW&|S}ckZ2$^_`orYF_S6y=^$%t?!GRGsBr|67ox3~mApqnVDA*6aKLCuxvczocLKHPqbYU< z=Rt3pv$9HtF8$h4IyNpJ48}2A{^_+D!h&lysthFesa|h6OWYudkN&kmb18^K6Lc4a zMmp{Q7d=+NVxA-SH$Nn5*g`9!cZr)$t|n}Hf&EDwjGdTtEQNimrS-sjbm#)zCU&vy z9kZkDyPG~uRw1tF;IXX_Di~`&@A<;Kb4M=%)tPP|GQ<@_ne~{jJm^ z3!YcjUa^IuS)G9Avp`BnJ0%Zb!!_+&JLU&E|8&n4tw61_#pn_NJ;ra`+p?TbWx1wJ zKcI>R^v2cnCEUHP)cvqJ84ko!onsEzDh&P^l!M75i;GWrbr=6?G4*d(4`j zCaI*W9C9C#dI%LZ#dvWPpyB7SnWWP8mlep~mDNDF`ad$|Hw#-LP0llPmUd95!-xv6 z=BTCm2n-Xg3*8ebxxKq_5G`>XGn3)qjrpm-aUX9nJH6;mt)KgS#2inkEf4nZCGVl- zSm3HI+E=a`Pr=w#j{b#{6RUIl;tdFy7%V3hk!toPWqfE|Uvfw^ za9%HKT_K7u#|{p}XV^aiVSHRma5R*E*4aoSze+1tV;0oPgbwo4K5Nul{ht^Vwy~LG zrMffXN4!t<;xk!(Noq0$@4M^Y+R4YV8x~9lj~bSmkxAhXT-nV0-L$vFU*CV06JVhW zNPqQB(n?djQ-=vRHK)6E(gp_l6%-skn$WSY6F={!HQ{>9Xjd0B*d&qcX7R&6`x7(n z6DsJ-KNvmmTOr6kJ5yzjr-U(;Hcnc`PDb;n#?4#{9oG3w=uYi>WgYU<&&s;@K?VPbReDZz-B7OA)z3 zWvm32jM2)&s0J~NQWabQ3JsxY?s`a{$xi>kxvE>kiloXEizTAOE8$sNFW%p^3d68l z%%-B22|dU#V7LF?v%#k)?&M4%6#D>II&~zeNAqou$S?emr(6jt*w=u|T=CokrOpwk z!o5V=Z>f*)MuZ-Xb?=v+D!$0E6R|PA0yD2DjT5!V`o9oC|4gEhfKlMR-krDdM+-iG zIEnoV%0PqOQLiG#Q(uu`KD`7i-V<;h<5eiY>dn?jAwDgQUT9!`ao8s4#i8w=V?^(GXb@OV1=*uV* z@&d`DYO8j;;A_olZZL5@MFP7HjL%D-;e`72(h*qU)Ob@P!qea$)HqhQWt_-$2M2PZ z<=L1i+F9B!{UcNvU*!1;dy6|Y5uc2aoVfLULQoWOG}C{kocim8a5(YeQqCb+`|L`z z_8`$5&~5_=(oR@9eya~lHRmAxFVS5`g3%Asnr^4>qk~T5`!9CMQU>9JSp;ND+)2Zu z3}rc1+dzYANXe6lJAz?1fLsdofLTk6>1U!Y_Q^v_S%pzUamy_m_{0~I0aj}@jV9!) z1UGSmgrA>RgE~#7TT<+j7`I|mhr84=ag5BF;a}%}ZN|RsN-5>~uvk0UQ+<3>OoLQE zjPXN$#1}Y3g9!L0dMH@*J>a0M0H3pN1NUgYco1K6O~KSnFMYH(-yupeA#_q>YvrWq zb?S&}#x&d1sWsvLkq`V=y0#ejiH>736O7^XPKoli609m^wnAYWtJi`a2y+W1DFGK)cvO`C*u@WbY}v_vh6O=a#< zv;2V|(4enJKgj3v8t8hks=uP4+w`qXd5t)AkIkgM$S-BMP{GB-&q=E|C5G(u zH3_V~lfteWt8LEATEhP$>%GI-eBZeLw5r;w>O$=*s&=V8tBP7pmz`*d9V>z$M)5%v zQB#6^XsYh#9L^?AZFG-{*Hc|NQ>RapeB%&VAguuJbz2_xts>U1l48!DPGE z{$lluFkNbOa#<&Wk*weT1WPQN^g6s{>shd_F4iDG;y7dUqEtktOYZ7>kOgHQ>@*uz zfKRf5=2@^(zJ;+4oYa>}{Z#{LfWtHu}0qn@cF>FLE*s_M@vxix|1*$_Wur z&EXwE=~|`-4ol_ISFs&6>?sjt34~&1h}?WspuZ5}mTB5&{p0fZ)}nwXmr8i=l$psJ zH6y6QTnr2C#~8)GmTT0>P~qe%wU(omxC44gdZX`>@LnOW0YpHk_a~SZm-8 ziB9HDi=Cxfe*VMn|MbajrfmoVhA%iuC_G6sAu?E&oj0~8OB}!>mwIpdmvjW^`g~iY zWp|UWzFgG|g4WE+g{j6JUoFg1H}ndN0|thsgGO6U2d7LNj+0Sr`k4X0X~bzTw&I#x zEKBLcOKF#zx|`X|_2cU(I6c`i@-3vFMQ<>B`Ztr$>tP83T%jT84B4310GP7C_qDm% z>>VFsU#S&U$*;vf{MVI6>3O`zl)Er1tR;3<)sEmJ`zbJO{9nK`4jCNJ%fWK>tv9xR z0V>40?w{Cv>AuztR`%JHB)){mwXz3*F;|cvTg_bq)sG~+|2rkUxw6IeYg;!V+@CBy ziG|+>I0NXPU!E%hQY@p`ExIa20t5;_iU&(pZgLXuJQy*Dv9*3ayD`gd$*J&Wck#u3 zIa;ibtB{%mOQQ4;J$)j0q~%2zk(e(8>I5B#V=R>Q#DRpp}>C);0s%inyTeQ z>=*6JQmwJ7A>{umGzRNwYmrEAhf0rspv7Qx@>W6?7}ywps_)M!ew}GQ|ro2viZ96vLhVPeEj1_U&1dJ zmdLA-pV^r5b>e%CDfaSzU@0k(hK+n^QmLa>H~YiNgt`UsCK)#JiLE_~CNlj|7*l7D z*ni#gF5|Q-3vj931*i1$w_QWSK-eFZeeQsf@>5#9EUnC0rts!o3$Atux7+mjX&X#^ z#1(gRLO(y8WAV|6=a_HIXYj2WiJ5A7e`jQnrMX<0hDM z&0OYlo)7Z8E6?G7gy5@8M2h@4$b;Ocm|e-H z3|l%%)e<@!!waFJl6LCtHN)XgrJ3|=ZQ@;(5K^yFwqJYy9b*QQ?xD(dQs633Nn zD{+PI0Ujb2a%O9ibs${*u^r%H3fl#(;@)mq>0wDNCE8WXr~XS_pk#E9t=S2p+Gc^$ z_lEx)P1g_aC1h@$8|t}VFy#{irPKDPaidA!DV*g#H@(##Ro6ARb`IsOZs!C0N`idHDAJxS zKD$rjF>3L@nL2CA-#yrGSa*FkfYB&OxI*>9V|#&wJUX4#LQCk4USYMibY?UO!7zTa zFrL^H3M7G9F;%3XtkOGcl5b)oc{?-)+334ZnNnHcTU_LGw5qkV)EwTzxkCD7YL6K@ zeQ<2$h4Mjhll=E$md2acq(y#Djhi4iMod@lg4BfENp3Pae|nz)AE{{&YmXHZmu4gQ z{{k5D=Z2#_rQNJf!@)_~x@S6Qjp|tE(8a5;d89d$34?CB_)=R2qF#Omd_I_%P- z<#+i2DRJ3FIf(rL6JyoCxjruRAY*AMiP6Bi$ML4rG`3kZ4)b-W>v0?u9t5&|!;A}6 z`YftsH`YT)5`iDD_w=QF>jWvinYF@f(`2O0Sf#8mdX<_Cqt0=cDu4RFYkTtnG zp+FX2XPIKpC}YH4G2(wm>uYOy4P%C}hG6dNvV>fs=h z>{PS0j3emS{b?2|vpc-`O@?acl6FsiS+|u-?dgg|h4kIUlI8PvUBs0)E>z?6&t(L8 zzHCEnWG?1loDKqRli-ki zlyxS%g?fh1hR2TG{$PbfP&7SFWzq)OWs|KwcaX^~X?B;hQZ41k2E+cIu}*Pym8I>V znb~jMf`&ZN??^ACp2it$fAG%o++J)n?4WRR6yr9|(Q7pqxl^027^{ZznQG3=iUfnj8pko@1NU$#3d>tMEw33kl;;3~eEr5LI4XiM6*h%BGk_erwe zX;yQ{8^}uvFqf39>%Tj8W2iw*CYFFY928Um(->{=xr%w5c}&O$=o>!Bf)`qU3vPRv z`#u9632?isqOcCM+ueox;N`kW4#dDU%cv-V|IdyUz%Rvvdukmpr8C4c_PX3Khff@$ zVupD)D?rlWE+ByM73%>M|CXV>-;Xh+XDOKByO+^E=winThk1 zafRn?PfEq`3hODER8wJZq+{8nzVqYeVAr~Pq8FO6IkKZzy8Yb1xcZ!gTAbaoH@e)- zrdMBuv!#CN)yTH%Q9FC@2vF01_Zig0q|HLAc&>Jm5In35J)ru6XRoxdoktH;hgjBZ>*yB z=Q<{OqNANkk89v7#Rw(eEoFDA$6Y!WLo&fNF~;=2089l5F?Y7g-D61yQ|_m}9s9m{ zZQcCTy(mUHpywG4J-kJ(mS*78z}&$n-HM$v*-9)-tjG}LC?+6 zxApZtRfyOaO1@TESct?IQl^b%kK?|u2gCe^2ZOafee$8zWaFfK@QTyy;2vagh=ZEt zXi8FS&JS-(?d4f7iI#n%LTIq7t9I|LuYma12R>Zou~Ci}HynTP>Ifa%+Nr27r=0P- z638V|Z2S+UXDi^=*9u;spLsoFS!m+n@^rz%xnQnNkvV=1c3*)`vE6{R7Yg}-D5#fW z{VhW#O^L+OK_4cXgk0_`EV?2gmJ{+SZ>8r9v%e5dIU=Qq6jB5zMh;8%+p+__w1M&D zyJM2Q#B1D9*QAl6T*x3>bDD8}Oe^>vK(%3VM6|{-Xx=lf}lyp)CZf~ztRUPx& z8RvgyIr;v7Wwe*es8s-W=`=3ib_HiI>d&=6CL($#D_~L+=~4Y7Pl0JJ^m3-`KrgB- zy}qNr05!ZkhJ9m-mDoY#3XK;MsuXW!4KIIXY)7@o)r`*kMzx^Vj_Ol!~4Rk@vKf+krxAsEeC%Aje09J3# zjQ4)yH330srdwa0uX6lh?F?!w^$y|bc%paj<=xAVv;Q4MZ+n?cgC;uhmC@QpA$(XO z&W~~Msfb9i6VE!hlXnE??~M|5Q?kl?xMx74zLP#s;{^`cjy)O+Hkd79e&}t(q%2yF za|YdiA_I)t3L>05ZZrPKDEGMgwU4d~hcY{F!61%?JEkBo#It>X1II#kxd#!t$z){ zD`p;DVf>Dy9ZQjf!&b9JmNBkn8hgdk)6J9yE(aLQSpyDAb)U5bP0|I)?^M_OP-BO=K_N zx?SA`D^>@%xhZ>kn)w}RH*d#nA;fp=Cl3MxeQ9HN-?N=!8XG#udlFM_!|j_91i9)l zVd}(jx@!+(G$i&Ilv38c5AqCe3`gU|1*A}uHP`MtZ7iPRE{d8--4U;S5#!G5nK`qZI8vAH$lR>8oJ`rv-hq=|^6(_UP-0*92zdCiMeB`{`{LGj3^A{T@ zNtM$rbtoJMZo;7lUe(sgKZ-WzABm2={P`Q;FCaI>QKqd(^51Q-&f3VUp1U4eRlIhq zCRF-Hh$U(&6cG*SGd1!qXj6SwvJBUq-7{;X$ixW@Qo1Z6C%3dobq^VeAQy8&Pf%{t zeVxV*8?H&Iw%3&%tJ3Q#VdC)%3UX1H5p?7m=xeu(bsTc_9Zn29VdXB;-RabD=Aqzu zTOyEWPd?1tKZ`~o@_idXQL7(pVE}I-Lu)YFD)z7)OUvcs5*kwtPA|?brRO3{^?G5m z?7@?@1m?#C=L)Bl2b1l?1rDb3IPHnH8@oT3C^iA!U>VEJ z5}$PGe8~?$G=66@SiN~fvMOHReF4nL#b>z zsM7RDmh#I~IxDs&cR1RQVi8Z~dC6M3PD4)y_|>a2C(-~)w8QMJZ$QTvv+1 z$J>h@24=e#lx;hL;L;px-rp7+DX5RCN)6S;a6kF!9;!by4Bf>S-0!3u+83((8%yEt zJ4x50KRuR~C8hAm4=9%K3@^Tj=``RDTY@1lpzlXN!frBQj@j==D9_j~E4C~%dAfxJ z-C;8H)|+wScluK5^lpz^&@9}`&NCbMi{DXY>M6VbcszlzJ?^Ux>3PkWW-9lBRfbHk zxcDT!-4009y()sQK{2Ij0^d7@bZ#8iXi!tQd-w3g;YFO}N~o=Z%nr>=gY^I2J_Rr= z>S{MZX=?iLp2(?w=l+8RxbbP;uaP+p8@yQaj2peuvaCm!=`SHF& z$t@C+(Y65jLrA!q!1>-lv2`f-8~6S*Ch&q`t6h|pGj%3VT(r{zX3h;Hvn4hhy$rp-Ds;`Hc!KZY7#BVCzaW>u^d~RiZ zogTOMKf4zm9|aGo`g$q~g~$pA>@Gh^(YVaa-KtsAkxl0OKrm6Uc4E)2V5M%XU}dwn z$u!2^khrvc$um)BTNO^VS6_d)00&`+`C;_6T;3HA?v3jvE&7Kc?vF0^X>aK3~J`b?(&1bMdz9yn%gdz(+Zr1Qr)wiS82Z}J`OnWl1XYK^1^Llcsm9C1Je(oM(% z+LRT{fMgnsCw7pX%IRpSei!|FMO3kA=B`vx=s^nXFTkNNjq}{H>)4#wjUe-RY(h1~ z_0NUKQWJGqZcXXW3}tHvW%L8*v?@CJBlulaK-3z38#!%d1Xlf7k)zhV+CL@=<+aU3 zasDUEFNZt)*8n-g6_ZDkd3steC!9a1fD- zt=65R?eQ6~a6Ax>f7^TU%K*rJpheWKzl>2zov4_ca!|0Zwvd~^zgZo!d-C8Co8s5q zG4;21_Ex-?%TsHanl-fA#uLOYf9@H_mSDfrHb+jN8YjOJghQ%olN4^9;ugiPXxKbn zZCw8RRQuTNt`D`od=ewFDa!W#&qa?&J5hND!RA`1Ajb^I5G0$i{!HlY^VYUF4Q@+V zZYkZ$mRd6#C1SCu?`oqCmlY~>fwb4^(34E!ekLfyoNRB!pT$*n{nO|%XrU{8>N@$T zn8Vbm9#n_gokb#T=O1^>fPS9_-fK#^+E^TIr0pkYJOI^-djF^480wUe4MYCe`_(XZ zR6NSvKkD+YGNtHg-=D3=9zy3GnL^_9beHSe@_(;)3k0nH6B|BzSNBq7x#s?d*OBi# zb#?VR-twpenHv#~EWP7r`s%wB;$iZWCAi_Gl`MnZi~HI=`cg)gDF#6KWQk#!+AYt2 z-m!O@3Yr36-)K=_>qW;HDMppCBU#prxvTw7PecRsYtT6XbZQ>A?Xs8k@S~}_((ab9 ztum|$VJ<5<8-)q4Gy1Hs&-6y(%m!HAv(pMX=z~vzh@cggXpd;EKVMXY;qi0--vBXy zp=GL?#+O2rWM)WN|6vA4_uP!uymaCj<^iR*;mX>3I6Q}$We>}*S@Csm;${{u9{XXn z7gJi!*G50wwsM1}mISyf`&n@3wMV6MC&#hrDvw;&w>TM!c8{m{PmsE{wG}tvw16`q zDg43u&&8M^zt&rsz0{tU-{+P3s5rssMsC*Dz^wx2(>IX1#>+AvZfn%-3FJ0LRW>e) zgi8#aINqvs;G0bmaq#+J%MQ^E5r2fRT@=|C$^pANRQRlKtxu2jaK4Tb{Oan|!+{ z--fu?vXchaOoL4j*2utHty8bvHQu%?vvAc4GYAPwT2!*VPE7Rw1I)*HZy26j{e9BS zXVZP=T{dep6`_OXqbd7!dK+V2h;ZL&v2S?4n~;}u&zS!8ZIu**6LG1N)JU$i!X%B} z`XpsU-LpfZ)7frVOnhf{=0}W zeYgukE-Xt5=7SuCo1xaHLA?iAjL+@XTR@Hy0KZjjA$5dcH{eP9CQ6mlZWzH~+U>sxp~rRdlxvsFd{OVc>>= z$kD82vXpS^Vy0!8E2~utd2HzGb<)(BWGW-JYvin}XU8{twOho|x&aeSS|w2cJ99uAQlze{!EFTe^)MFMtC)e|Ij!EEu4cR6B;mo7 zX~ZnXnq<)d@>gF=gP3e!DTsCzJ2+ptU5m|&-rFQ?$^Um8PN%Fnd>65;*_1UW;{U(q zY<5wCTB_~zGp^{_>1mt%&i+7wUoOW7WZZdqmjVZ)M6L;brhLXy82+sKy;~f+6YiPp z);yj`=N=`GNC`0#o#90oPi&aD}WONa^v4 zpvOFW(SM?`i@TYu#=Zg_O8T`)Ya%=;iC$aq)xQ9nQ6#qjgW86Cj5C2iD~~)K7iN+8 zlAur&39SCM#vi5sa%5zOmx6(1GCZ5HzRM#j%{%BC^A%u;mX+k^$SAY?`K6QscBORq zo0JomdPYfx|0c-R}>Trr_Oks-euA{U|-l(uJDpxHRMYf7=X7FlLlW4K;^ zlN|HmH&HpEk~sE0NMk`S1@k?&9(1h8UzMH1RoKM+BPQ-8eIwN+?)f(rM`If+@~N)N zHNkqv;fu}h-5w6%BfD82$Ws2SfI7`b#<+iOP(AABFx|VC6GLC#r@&k(>Xhpl_iq!+ z%QOv(_z}Ec%Ty>94$TGJwC9N)XCG$Py@`Wvv4O2#PsM$SA!rQEAX3K4F}UftH{+hm z6U1fN;K@gXe^ci416(QFmxR{J7GDXV#|XYF z)Bh#RrS|s61h{c3X?JP+)550!d@?i?^@X|M8_#7;iN#Cr4#FNCBFQ1{_9kzmI;2?7 zjNIdZb|7*Y?%OxnqDM_<-;-A(jQt&M+Z5_i=;=m2g!r)Q|3$7jIeP4z6+&1~QVzW& z{BR6ThQGD~ermKfQqzNr+L-K~<>9uLNr^gQ4+nTT+hb(}S(hU6bPmpT?URD9YSPINZHee)iWgF`N@ z@qbJlQvy;j65ux<9L~!&AHT-{-PV`ROD@@*-v#QrD9(K-f4qHVhX@p<@YCi|LCO&( zy!hlNBIQq~Fa0)9;`@4vcE!<0(WWjIpp2U9{o*&tA0WGGHBvRtnvO3jdBqJ+o z&+Q4-k*noW(YSs=kEoxpn;coN5^B^*|0@j}+@^5Z*P0U@ccs3*nIFa-hJvdGEHLG} zn6)aO^9j1L+QO{b-ztn$imaLu+Y%aAP;9Q9tTr=B3z5Chlnpi(5}t@%(-y3jn^_xO zmVGdi7Cp95VHpowlG?U)zrsJZ%HOdxozW{jAWb28()g7=a z2w4<1-Wb5Nm=4gCmW|yp7*k!i618^;)+C27tU*MLvzkb&RC34gM}2!+@w%0+UU}Wt zjg6@IlcwEhp_#u}*3S>|okG&_-Gg}|xwB(QM9^VkbiL-6Su$s2txu;V$4BQrXGW%n zN>7Y?5x|Jw>1}tZF6|cqTj1-RDc#{@1FXW^YUT1p5?4J{$f#KKOJJE;aMXD|P*|)R zAvMvn$yON;T2#mH-g=apD2?^g9^cYWJtEfkWq-lt3j{Me^4i~)=#}WZ31JZ+uCM?A z2mG1E_FJqQ5#JF>eV!2;y|<*?vkNWUE{u4%+ETsVFB{M&H_zOCuKQLmujt-f zeuG<6N>5L3ZWB?Cu8QiD&z=lxn$c!oYAEjf`60udPKpWmk!JKzfw>|wip3iJ)4s_JM7EoHH|Ig(C_{;7^)#QBPW@xum03(2oa_cp!$Jj& zSg)te$wd2Hrmzpoq265{9n6uUtrpX;n24&IDGxhR)?98uL!HRQezCsA22o`V?WWzR z4)t}Db-wteh$*(Q%7LIUH!(+DkBsc7CL`ASpPK$9amYjyZ2*QgqF`N{F zEG{(rDFCVUSU`ryS?Jo@8xu}V#_Ew`wVE=e>wB4YO0wJgE)Z3>8d)#uJc1t?Z z=L-192>;(zi7Kx?bDaGAu;K>WGtDVcePCyoWAJw8tI3`CF6{20@2Q8*^KC!P62!aI z8|blZP(v6yst7cRyRyp&Le~Gc0|oxHEPOGC0vq2|W|sgrAj8K8-Ka;fOXKR3ol^zC zpPAt>oj+@W7Z1Y+A|oXudG~pIXnu;w;T5*18_?xAxT+J6BRvf|;8y;{wfJMLtyKa- z&;eE8-Z9U7_=!*Rf_6+XhQll1!HIRXzI*(6U}(U|RqW}LC5Z#?R+G>(tv!=9Okebk zU|fvr0+L3=Kx0WX2`R3O{8U1IV;JC3Zn~y3XPW)u}ax zBupZA0|l_6CdM|?`Cyfs@1ie2nPCj)L(z(iFuAtAQDqH*p6dEd@ztGp-V${(e#Oi) zN2AG&W74?&IQDWZ-{+Ei9A&e~bQ)KhP_us+-G|IG=Ep}9&A60jxKL2&Ux1s-={{!Q zG;@5yJvpi3)Jn75!$xLG^~o(vQ0{Sy&86PM`L{bnjo02uBuMfaZGp^*K@}QL_GxTW za|BqRb-oY%`1l0BKzTN9B)k-ziq6xK`(k_jKEj(t@^RkX?$un+%ws3j1bNCrWN}1I zN~fdT5Jx`zv?L#xaNg8$E*w!xUsvZm(y^e_8U*sW+~^i8ug+8s#p1hJoSJav-ysGuFhjC-*DbI zVE?hy49?hi79q;fofFDTqi+=g(0KRc$1;afn;v}k;|`C{Z8oN>r6O0jP}Ga;u|=sc z3-ok4s>8Tdp>oku(oDG}-Aln>Fp1^4%W58Kbm5t>I=(sfvyXk;eEFS|I{VNZxibM! z!^KCpsG&&`Z9;^0(<3HwH_z&4k?`MtI+uCYOP$ZI(|Y^i!5*svLDL_l+;>M#6J~&;mvy|X?^yABsH&bi_FH)5@>!JF9c0sqU%;-H%{>8=6 z3BeGP;nit{U^$loQ&&o~)OwV~s7 zw>I?T2HX?Tmzr!6eZw_6p^a}b0j)|GWxl>JxF z3R6-Xx*^lTP2@qc^9c7?E*^7o_e9^LWIzAwW&rPM3c6%4Rx%7C1EypfE4;*ISQB7z zpKV3Mw}q9vmJ>th{jW!dMc^6_O*e%v(i@W=btu2I)`3xxxWgr@j)wAh0`C{6B~p?t zH>xMQC_18^b)lg20@KOcDs#rmi2?LK`F->nrjGdF6_1t>tLN_wvp+8e~i zLsGCje80MB`a77oVYxQ-x@&(iA+4&&>;P)#vC9{|+1CG|*y)9u0azVqy^JDEk!62h zk7S3lMFV8wTZ2nw;S5G=8@fKq)}9W2w(JKn1emQmw)5Vk-Iy3LEkN=2o?WBfU{^!@ zRCTk8W#znc^yZ$A>n}Lu!I=0{UlBZTuI}jkKrnLDZG`3?&~5u^O&NLV7;6+!4bDn;4HUo|zcmp>!Gc z^Z!&Cjv6mNli9c`4r(&ipBOiK+PH;i{YEvC^8E3i<4jMI(V_cNeLqm2c2zSX1ocJZ zs`NJPXirCX#nEv{AZIDF*T8IE4@k+Djb?eMH9dlF9=HiYu8*3G@g`GCIB)Tk>64LI z0`Ik(zNQ1r&kv$$7}u18Xy)oYN-_-O!Bwmp)(D2`$yq!iuH?@In91Z7@wv^t2(G@z zKQ5&k?RvFGW~!8k2=?aRdpX#D7CNOrzKMS7)x0yY8qe}ey*T_9J@O0rClv*B<-6pZ zU!q>#v8mhcRwYfBGgry7gze%mGNvirMqd95C>(kbhZkI>%WEi=@1{s=?z+F8V4)61^b}!1AruA;s&x!hvQ(?u^%U~BnC+Fkac<`?wstz+b z3p3@O$wUMHJkC3)d{9;vED5mSurKRSs!S^ahsb++KBCevl1;yzf28Y~fz9Etj%p6QE1m$P8 z7A`KRR4G z4=kLsL<`s&8?T3X%#Jb~Sijw(IuZi1fo)v6d4}X{f2>+kBB?0Yv@i-cjW0nkCnI2v z(E;>MSKmS3k9qp_+`tDtoUYZuTvUi5&8qLpAM2+miPq%O@aGP3u>B{ zm*?BJqFaiM~Yk$kCYxcrf~rh>sIl{K8yi?NdhG^39C4Ao&-g`?Uo8LB_) zwBmotc%!StBn{pe59HhENeCp~_!H|YkjS8WWx(J?-p~JgMKE*l5b^H0a28sFpm72b zfqgobNPJw-=daWemBlxik~eaH1Up)WY^ie>QdY5pbj}~xG1f>5n<<>UzQQ<0h-=aA zE3{{JdMwR5ogR1(;t-1Q$TjM7>rVQpUm5)MY%ObUOrc|O%jK}#!o{VDe6l&Yh*r(( zgPFwL8=coL$YDuLg#7XCbrKXFF5h(cr@ZzLMaPNVGw{DX%=RCPGh7Y2^#*ewUP*AH zmV`x2*XOcVK9y#kiU~BUG#lt&jy6~3ap1`Mo|24#HKnjuzkNl5mt_{|g`~MwC|j^f1#s3ef&yyvn~L zJp8d8vP!Q{zg)v%69>cjG4A@Jy(H`wlVqR1_a`dx0+a+#@w82sRK{O&v?v- zq;Cns$2FsRk3I}AtHSl1mwd2|U=t>y5M;=|083*LhuD91rEsVd=6BE>AQo%=v#`D3=9R zDc8(tzb2XsRCv9K4I9Wx+yiZC1=!RY=d;;i4u)ZBe&P#F`8(8}yl(lwR|Wa6hPh;< z&FL>$-_y%`IcoPoSAXTjPxF*z{T9cueuZ4^2fskOK#DB_SQcrr;jYd6FogHY(XpxI z)jr%yqT(-)>xaP#k_^6`!5PKzJYAg3K+%1vMRqf-4VPmY(04C%OpTd^ zL^s)UUS-{PR^AWnx$~>2FI&fCjh`&GYU|+U=f)lz!|8@_-C={3GAZ_Zra_-CYt=zA z=g;h>5NKUY_bg$alm*yl!GUV4*iz<}znY5hm&updaPPYma004|Soms}xS^bNU`%euZFJx9OtZh#s^-xLeh9 zL63|2PWC2J_u9kaR142hrB?Gz%5W3j*isL-mzXf$)a9%Hc8^t)$TFb(zKOHl>R8Oe z)#J=+@;9CMqUbE_wWyP3VM$B&e}4gX{fh+=OqlbuUVFxi1q#a>LE%pMrbi4NO)VUo z2nyJKd_h^fjX}C18Ue~xs!*+99=Nm<$rd#Z7#cM^)347q#1d~@73e0 zBz_fsi-J%+V|~DoJ*eC_=HVGCSCO6p+I+c6E>`u-K9n}YRZNq#HGeDJ>(Pz*Wv}$?Mp^Hx@PW- zE!|sZxJ}HY3Bly^X;NGOc2 zwLYKG?05r?$8nj~N9BNK00k}FjBUK!Y&L?l9)Ge+zOz!3nO##;e`(u@B7J|*F}Iqm z^>q&bJ0u&fcb|wz3#PP^`-rn?-TcDrTG!1}9MhEk0uuTtJbliXY@7he&(7%-=|YH4 z*+9=WxW_y4Yn1JmynZ7?wV@$7oGMaboNQc0Ey^5lZUIwhJhyq07x|( zs%}OxhsVZ=(=HvsKwEM9P)63R(uwK3PiI&WchXIVQqg$H zV8Jg7Y;NcelvcFL{HB_9oQ{pRBL1T12L;?F#U4kX8*2*8ZEfp-Z6}M=)jobA8M$P8 zsU5c{<5f7z6BZu)XT$w)^5}5MbX=S))%qc4u+KaE6C22IxpKf;Ag24KZ58K^UUO+; zD#QtAEj!YtG%`0YuCQsqA={6QBI_Wq+F9eQi#%d8>CFPMq zcU~LMzFTfvxlL0-o!ZI7vIOiDeH9col!l=W(=pYoMhFV|!DBeRdmNMf-hBG5l9^KE zWebcj5S(&)up$>0Tkz=X4(M<+_nWCEm_c%cvZPzJKQGck&5yIk1y)ZMPF1urkU#Qq ze?hB<)IFIvR%JFG*yBOjdKs4O3fg?yEG>1ce*!7%qRk;_K`uqaMwj(b&%xo^9-K}s z&_uNC)v7V0RzrjL>@_&yny_kuxT6bJH?E`a=w+7PU<DV0`WZLIg3f2d5?yw$H{4moN6D3z-L+h< zFRiQq1zzmqXde%w`^~}}^>yXX)b>%@$s ze9!2wyTC6sH~+f>7R5-2a3tg8j9hT>*XTObG!SE z7Cb)pISiC54DI-HWDrqD1yH)BtLjr%H4`f7<8J^gLfaFYZ(m-;*+NpFf*;*sl)>;e z`V?*j*}?bTd^{1|^&C)VOMx}Acxx?@51g2b)ReHoyW^*zK5=ioy_fieYsvQ`9w_O|#cl3u{d7Sgh{c6_ z*3WLPnXCi$g;P(omDd$ueB=IGrap+OJobBUjZQ@rwuFmZ7|RW3)w}EEbI_XiKP#dj zwE;JpEpUD3;}cZoj&T#lb~5&Xz8M4cdv{nvZ-uA2f79p+a$o5z9LG!k`&j`SuNj4u zog5rl21F{Hd`9}%Gz-EWP!yAy0ZWv~rdLx{v8i?Qg}LK6_FZfGE6hUfUXO$OqfpwO zU*T_ z4WDrDviPbanjFCcnL9>V(33?`LeI@cmRZXe#>c8ae0b=%@o zCOxOQUB#?#822AK)wx=q#5Q?Vf9TI;%$vZr+wV6Z%dh7su5puL-mevz4i1istCerA zmf_x0y0KToY7{8|o`<(zajbu|z>S>x-Lb4Gt;U#K{jw=mS}ipckVW6K05o8U*#Y_X z61UM&sxRmzP9GeX)X9V?Ts`lE*aJ{QvM1pXb#&?LEB+B2t$IwR$bBnp^G{))81`12 z5PL+*K#an`*y1xLddST4kw>3bp_j--S_`wLMX&x?_A1abq?RwNDDBK|JXQSFSb?on zg^JnF2lyAcZr*aXVGGh)?KcgD=!*)NhC3#Q5Im;a8&!8B9D17`-E$dF5g60Dc9X~J zv#x>2>AszYH_X=777*}6Bb$lV1lKpcNx+;}HRsl}SCtwqKC&DSZD3_x{)seJq&9xV z5euxzfV?eX5pv33==99A1wTYz2x?MN`$#qWLD-UlKR$1VM^}ABiXJtx_WMpt|!{Ao?k6`T=y4{ns+|Aoo$OP@Q%eXF7SVFZ)Wjo9;&H2Nr}X7 zj}4!Ijaf}&m6eK(3tD4kMy*TSGo4AC?@ze-hfb0|YVi8TX_&l@F?N%A_v75w81{}n zfl2=*dlFXHugs#kZ}t2=KbW1)Smw`#gXzWh44gK*teW@i^t9ialtJFu=m8Z)qL>Mm zkorE@s;dnA9ZRfHOn^dFi#=Kja^wC2q}6{a*AJAeoVoc~GRhgQ(+Vsr=^iMXV~2}| zPIB>yN~!uNmi6N*KldN}#wWsXQY<#^rRyxSq~BW$e*EzMi^}2sgEgtd0-QmWqCmwE z&W0^c44N`}%CL+oF3CbC(<$2R_aA#pwo&5dD=G-3;fP-Vz0cOQy=NTmbtR7S8P=5 zLtyt?yUMNc-T+^5)+l$OTo9yqL2EGXBT`!_ASSD6pMm5ak0$g}%_ z%M7C--aC5+CvFU0l_z<5BRMIM{r9NU)O?)jruue9`oWN`@xboNk=~u%Vrv7^p>bVe zQ;zY0?MQLO$^LdpBa`h$xBUiV$-yH6(BLegV0btu`Pqe4$?N`lz|m@45Pd zP50;ZD2M|6xK~Q{q?D2a8o`1!NgtEHfISwsz5U@<;30(+?)S&e?3=^ zEOL`e^kNvA+_%N8m@+J_VGc=Cu9E(H|A)Od4{IvP8i!-6ZEL$U&7uNI4@`rAfHCar zOv}=v1R6sCfxwVRkN^oJfv^afX*USl(8`jqwLudJ5E>)I5F)K?3IQ4+AS409zJ+~} z{kz&dJ>B!0e&65od_8}B&u2YwQ}@=Xb5EUn>QvRKI#tY=W%7gG6J$3;*bYb-TxLum zF3ojJbK4(!lxC`>2do!P8gN}F$<0}U!v*=4(aV)PARU2u_Z_c^2eTs)?DTxTv}*@+ z5r4wJSmZUaY^l#=NNsEj4=HWgKH0?FLsuSwtCDwmLBAD7>OOpzwEHK3KmYW**CY6H zAkO(u`VLAz>;i#cdcbpH05UX?=uZd>2o3!rFg7sM^2a9z$N*G`fr}v~JP5SwT|U?z z0Q_8NaCp$Oz3&hLDBgwrKmG1;j|mU_8H!T(Bh*icKmQ#33Xcm9#1n%931(;Z{{-|; zP}EOgyS{pd=c@qX71%!D*##I8AB+up7x%gth=$`sVQ)cU=D_8gr7xTih$r~|IW#aj zIKbCFkPt$|h5HhNu`%xNif4#MhWY*<<%;;Xm%F^h2n68eZ{K+_F!XT{XlV`!Qs$=@V~sxc%=T<{I>ZC+eePUp@G^7;B?B%dge^EcmjGrl$$?3^e>);FaJnWATBoSQmFx zEU@bSvZkHwXV%e{(P80Xfy98%qQgS51j}d}&3DKxf%x0UMw*|!10jakX#V-_TR-!H ze)ZWo96sMYjXOtvuD62MrSOHz-J83fGxpiEYF?+*5kbk zn!wxem(i9N?Hzx57jS2z`BR}tB$6QsVu-^B8G$VaH9C9N0Dv$6cKIU)L>piU zTK~iV$TkWe98L_zVLy9|F#w5+B-&^Kl>S(Ru<)PJ{zqQ@0uVTX@}Cs^N6@gaw+wiP zmOupR-2V(scw706E(Q{Ck$6-f0Iu~mbGYR>d|&_(hj+)}LjTFdudd*TIKmZN_-E(N zz2n+vr=S6-VC>t6y6H_FtHMaTW}A zFf}&^pLH}fb96NM;*9;D&z<{cUV9uWG7J#5f9AdXue=VwnfI+ggoOidwhzPuQh(VI zj|=!4l#_u=hcNI!+$KZl>Pf3Ry8kOchyobZ1e+4&x% z@X;QiTJPSa0Qx{-*KUPfJN2N~KuzEM4mUvQk73W=eIM@rVAn^xc7Z;4m-NpB(C&SE z_I?PY?EhpJXxHu!KG^@k-d(#t+P8P#UeK-&cJEQx`{{>&__~PGY3#1N53KlSw*XD0 zvk`yS%J>5E$44eAAKgXb&wUKkc=YMTZ|sTpwdsUPons0BwhwlH@X>(}_w4^@ z-@d)?Zh;Mb0c_yE-|e`lyzkF$6`Ef^RD>v*1Vm(iI(yyg@UQ?$-8vz_FT9c+pRy;5?6VCZ7xL zD@JWazj-8zmAk{>Ew`a6NQ(IsHD8Nvn?4IqwbL>!>g}UknX`v_+1t-K9UQ7>EgPso zTgE~-7>Xd%JfIQ*cXkv8D7v>nu7{%rZ`~5iBrq$Q=Dt~paxHH6ys(dR*2X2a+BHtH z<%&v|oyfmENf5n^LOTJ8f0>OdES`?o)Z#ph2Vh);&zCJGCv0X3%Qk6t*Q4Gc_)9PK zB+~|=^Ap;%wij<v9*8!M(YQ|eK;`+xXE-hr^^Mm4k?W%q`}U(sH=?FgQk+w=(&%OoxvC z)VuTSQGJS`50z^jQR$947TyT9J~n@*QvGIOkPf0HE0~8hjbId)9H3(obMo|(1`Esw z3y!woX#B33@z|C)O3c-VQ_JKW$x^(x8?5qgKMxZA8<9VZtz8hfEL^X$nZ~cFN>L*{ zHX7cURX-9FFd}HMesYOeT`R=K+LoOv?)(0~LBr2DKUv(o-_MNs!sq0Q#nn`7#lloU zdf9ogSu0|aL_D+5KUm;@x*Fj>YG$M1amQW$*Pk=~HzFU!Y+sy()!a2mk5hl*$n{!_ zY~dHtO8zY+Q1B>4`%YxdLyRDY9E zMp?Hlg3z&C?1Nq_5xC6UJn18WwTxt6DA{ZrE1G;s$uIqH(C{S?my^JJ5J2Q3Iy2SbXZzzMSW-lKcTqt z(6r@fR3BZZh_RWF+U-xOyzs`kLuEXtNp)09p4j-HkewaLSd9?G-idP!fF>)Xi9ACR zX=gTD+`|TnrdVB0*HYayOg2+%J&o~pn@<+I1C#GWpk~zNg_U>}>-x?zv(ZMGMK<xj`}I#E9M8U6f(=VeQ(A>9W_MMR+rumOY$3`=N_8dOmO>PZ$pm z^*mbE{&m#DMdS7p=78VuW;ZTtQdaloT83O?qj)<*s9sksW{?k%(^pMdqdDusCTD>& zw#{$Y*#)^}Yj|qG=g!87A1>>sRxe+bM>KJ!2SvV_uq?i7PpUucWnr2vSyJYVv$OLO zo8$+nWo=u(nn+wOlYqNod>^og?zXg8 zpwIvpj!dG-srDPOI^P5=;$E`}d~2WV&8JtH3B|y9KP^ptx?9nwD&gx_(znthLt>7n z=sxdFA8y~0oYkpm9jH{Zu<7ZbTc7NvVB1e`SVnlc&7F|jHLh{SN-~3&xeI$PG5s5( zQNiUK)v?FmlXpb6SRVt0O=t2=1#&A!cO$p5`f)+E{bnh;jyO=T{1Yv^`OqzkfJ7Th%IDCezuD-{(9QD!a=*3T*)+X3A_S9m?9t-;p^ z*KYSmZVvnlZ3n7HCA8Xy#^+pc7kGG#AiI}2 zPA$M-I0|h(1GZxjk#LfKH_iyMehC3Aah#dyG~T+c;bx(+XuuKEv4EfCvBj`zbYu~( zmLIlpe-bs%9*vh5g>WEm!aF=&d+si1Ir8yAs`8FoA3}_qdX)yKBRLnK>2u2^aC5X- z?w94u@)3>)&s00N=A>?u#vTEAo!=(5xvDOkt2GaBgFew)u<+E<$TMiXGi7E7zZGT8_fGo{Js9aTQt|y)B^E)>C0}*Y!7IILS&Lbt z))8~dp8kLAP+T$zJyExt*ie~Ug&xG3dlvavL|d5R=dtxf=f3An?2(cT#Wm*&wl^PL z8x%`>P~HFr+g)abcEijvYCSxyFe7=b)VZp&B>bHud}Jx78sU{vFEeRcBZAlAa-@zP3iDzCqEic0l1K#mr+l zFBc&x5sfM#c`;`ieC7v?rmc>g!pjBefqhYNq?JRokxA1s{*e!ISM|j6ldO^PdJe`= z!eTMdtV$|XWR2!nbaS`18U*j&Tu*HYTwtyPj~L@D8&?{EE7AX zM`=ij&_!u2>OGfnn!S!2?=^(3-duTa^zx5D*nOd9ZkYU{Sw!h;rQ> zuY3hvZbdTcNyxLxig zIo)gB5Aqg72os-XOXZ6IA6Q8ny z9Z-lPTGJKS6RJUf{kZ^t=Y_>#=ac;o%egXaw5K+$q+S9JMWa07ie<`hlxA|)C`*T- z14SmJ6Mfkdp_rPK^DMl;yuiFr-SwvV{X)hhEh{yz$n7QM=mKwpkZN|i2hP9Ko<YcA4;%!}#-jwM(;F_uvbOX_VgmWgCDElF3{u^_%d5Fs3QCQb z@aD;^qGYw0DdT#{GxI2jk}qXc3se5_O8axk)8^GHb!(7peetvif4Xn9?CWIl&AJRY zT;#wLpbG_=N$_6g`BUAcz@B2Y>DX+_iYAp^YnqdSen4d<&bPv@dt%H{$9b+5%;Z76 zJIf@S>Www~F{^L7gWJc#Y0NLXwEJQ1%sS&<7AB>N^pOmh?>DhexGpj`N#*0kt5+{v z+VH~~%5KE@SKdyFmA-8w>JZjZlPt}VYC4h?biSCSt*sfbz(VJN2Xh*A(zJCbNYvNJ ztBS|!-+XS5sdg;6i#bXx3o#(;7WbB*M@qWj*b&7+7G+Q`T^*$ku2bLNZV>+S7yX|i z|E)>_teZ>AIr|;YMN)l(21sQ)V6Z&c6)@{H#&Y_jTBGp zfbjD9joOlxAs;4lIsaq3$(34qQP<6VQSdNQ9KC(hvOfNfz2%*>;eQq8Bq_|h7BiCD zE$`>VN~73)d%3&&iP`C8Zt9DL9rh7#Me}@pjdSwEu~XWNO5wd$@tLvL4BA;b$N8tP9fm zkY(hSbIBlcIr&Eq?zuNl|pzFCV4W+H{I2#`w*L0(TXBa-zF7DN+;J&iZ0RlIE=GeJ# zBAS=AVtZ9wD+`+}LpBCRhO}(lgDHiOj@Yth#7%>=A#Z&iLgh^{y`(m(oO=@aSj-ST zf5mr~xgHjov>(}9J;%fp*L^J|iutnSNSGQyWsYRva`&F!fZ;e{px>sW(nY%A-ciB2 zteY1Np`L1F6*F(z>KJj+0$Ry4ik8Z^OLzbDFLwWS?Dw~H%s+q8RL}8qgm0L48#Ho_ zh7t7E+z2+UCu+pK!#Tt*MbLreXF8}1V%S#|j|r-P|@ z(dM=19wi%|76W$-Bt5QN|FhFwp5-G?Jwo9n`kY9O=Yma2wOSwPT%0}=n`-1nvyR9T zfLErCR|t|rNT{pRg=Qf3Fk)6dRw|FStKVr;JWVLpP@(03_0$AUbtzX z&rE%D`j-vS-l(ZZVY&Ulu?$hqD)N&Ej4*$u-+?z6MsBwu%vjp=oU7cvwg)$DYOT_K1eq8aXSqE+Iy)Shtc-FHo3tuB zrRf|#P+UT()=Q#SM=)4uY7Y92e6xLicwVlU-Rhg~;J(#b01PXx_MQ$_5?KYdYMz}x z-KkKA)&zn%;NSe|`u>$}Ewu>5l!69H9SbCl!rh0W^1!Ldx8tyf7KBM-N1~VR*OsP z$j0fPtQC76kqeNc8IU0DR&U>mC`}}hWfuuihLu4S&x(22Wre6#H$?GZ&fx2zG<9ul za~1;$l%DY*De1ArM3P^^JSXQZQv|J| zjx(&~>dJ%D&fzEp`|vkISPA^3ZpsKhqe+E<)~q`U9G7BR8@E<7mf2?a$C*qA2qAn7 z8`ckDZB!>r%28pfD-s`KJd&ozcuX%yT*su1&IeT?`#o|!4D9C|l$wqv-#WHFm)P(y zZHbhvk3D^*BI|h|JxExIp)wMvTmrV$B;K@4oDz-HQa=%xzD9l%q$&|}=4YNdKvW~3 zHME(Db-!}$l8znFAPUaLWNuAm*xt|>E-Dfv%o|8nvLZuUt7~D%#0_1B7S%r!I3BD} zNm|I~PRGO@jx=%bFgsKfEtz=H$g<5u^-9i?4n2{2Z`&b*?y6&Wf-=^q&b6nhwN+uz zYGzhAk#}T%SAQC8B}TZMe@^Gtp}bKaqa3cCuV+Ze_-mGi$J&LF!%!-}c~eHnk7|j8 zyH8t>c@9V>xf_wmYVwvY$qONGiLS@Lk&LlSkdhs3NLTpk?(zV2<(S&Q7Gg~8a#p#> z^JaClm~S;8d9oRhIgol-AQgqYPT5XP_0TNd((enKWaK9ns6IJ4Vj_DAwRY*rvRK@g z@VxwrYXokr5!5=3J)FttTz@?fpJrzE#4T+c zQ!&&@u;Z0M=SO^fGg~S{OhUMw8_X$71YdQVHcn|@7nF}bSk@)P|KMWN2WTKi&m#}3 zWxe+<{kqb=He-KP8)W$eHS+PwO8yS$5-&R$41P5Gs1=52u}ZUnK#d1@a2Pl$Ux!jm zeaTC{gF>|QnCXBsSq7sFO}$3#E4*9MP&vLZ7a{D1yQFHQ?MlUy_|4OKA-Nt~ zf4^^))tU)DI^A|>2b7-bX&)HiX=2}rFMP&A6Dh?s>Q&M4B$~E%N~=bK)GG|OG^DQV zW>0tF=P`Ofn!H zm|)QcsU^tbwEN$%{`PCu{D-^W+XQ=%JED;>mT|^Zaws>XoXT6JEwICUGn%898;+8~ z8j~XkT4tuR%HSK{O?P9G*LWsSWA92;sC%m$xbPn@I;p@Zw;qib(^P>Sk%wf3Clzl| zil5QG<>$~vZn!kLuiK{G(~}nKnrPhw%Qut)e@V$$+f_-}BDZ-XKi}*y`Z*WEomMe) zAn$-QhV#_A8mtVPZaMVH8;G5bj-JYq5?OK~hPqgt@A=@3@4dLwDG@MrMew>L;iNh^ zFK9mt!_s7s>!mAx&D35xYJcKf(;nF}>X=NHlT>m%F-NSL1-N4GE*L%R)yMm$L1nQF zdfZDCFFe$8GNDbz?N6di@KSHPWOgdxX&9Q0w;n5pni|YN>aQ4#hHY3qmMNw}6mPi0 zU$ZHXCs@`IO~*Rj;m)m)zmnqnJr-BPihBxvz`7CsKrQMWT_8=Y@T}$3Mm-L-!ZR12 z#K0sL92oB_L9!z9kfE2vS%yxVc0%ROxQI$j2|r=XK?B>kb}i&fw%mBjV8*-xyqWCp z1yIzTj|o{=+qi?~n|<{RLQpjJRJdVQoMw?Y$QZF3GicfqKWY8qS;1_LL4!0uC<-NL zn|#1?9EV5cdZ!h_{a&u5=JHmruxbS)t%OpPE;T15=wmZeTuer{DeDuEvei;VXk4ou zA}YVpcyqZ;d2Y}I|&bXQ@*DA#6Q;Om>wB66PEF2K#jk_)9L^mT{1=px1)H? zqru5B>srSd;9gdNQ7|UxW(mP||6*A?)&HN32k8>CJIi7mjt&eCtv4 zc2ngmuDfe*d;dgEdmCG4Rl;|$gw?sbG+D(Oh*NH)M>_QvRQjnWlpF0E@4T7*C&qRZ zwVRM}L(H*KND;zZRgByBKp_p-2aPm&J%WkGNs2wM$0 zd2;_?!T#6O*G+p1rDoCGI9`8FJGyMsZ*nXQp4qh!8DNZFFsl)Fk5Y5egPmO;aXi4l zz!aQMDWTj$N!eS;B&>JxQRa-#izdcno{O!YP2c>&{5Vh9Rm#C>O8qfoPW^!At=7%Q z4&P1+%9#yQ85Ox)cQM~d31~C~$D+ZnSpZjc_U9q};=~fQ_;W2pjwTM!^b-?yyw;?dwjDxW4uFe_g?5{^Njr(7Y=DWxmOvm{a81?i*53BrT6| zoiA$c#C>iRCn#j5t7ih+nmfQU-?Xs}9Y@xvNjsq3dUm*_CG7Etj&;ZUgTCeKm9^s& z%K>#HUGr7{djNElZOdwal647}8u?`dMF#gq4B&Wq-?m%{c1$>DFKqvs z7Muhaf^`|%#femdUZG!KN@YpUj2mX;1zl7J0R%}=tv|F;<=({w*%@Bw7W_~AtjS9#uEC~YxEEB z`)9WEkdR6^c(8)}Camc~f8iQo?L4_Wq;GmD1VSM9k#Q5&$Kg|ubifHJ5=YLQZJCy;FDb z%lZjt#qB?=?p`vOTyF2ksBIb?Ogox0n@${?@o5*fQ|fm>huqqLrqGg5O7>WOp#s=! z7S)|r*K}Y0VQ~kvbaJ#^@*+eOf9Yd^QO3YwxvR7VL)ZazHm!x)5gRyFvucekID*(L zM0_&d2OZ_te%sG);CT1^{DP4e=Qbi&Beq0oxD!fHnozE9a!DqTmTR8OUs*9JE#t3* z`X}Ykmru6I&EaqeV5WgM54nI9&1GQQqXE@cGCJl~vd0pT1L0kpLrq{qLiSgnbRFf$AolS#H6dwD|>$9)pq?St`>DGhvUt0v%D zBjdT+Pk!Zq!Hdoea)?wFM`#hxs%&0GOLwb^&qL^u#*j95|AEEgGtjN)#7wM+}aJ_Zw;}Z zcfCoeV6fG-*|!8IXN>bTw6Xw=-@=6mx-r?>xh6ezWUZV!jKIo90aB6B6)t9@gKIH> z;jkp#GS=h8`FFMHf4cn6qf(^5afkmI)B>ron^DVMIAV-K;v%qUy&cf?Dpj3?Q7b~7 zbp-BgF+`AGfG7as7s$2n7Ku(G#4S6OpG3}$be}lJPt-NEAb|=yhA3lfBy=fj|wu#jAUALM?d~9lf1bfK(T} zk=4fL8_W23Et!p1OZY3G?A^I`E@kz{ou%eAFHK}6z zs5vV!H8tCvMp1fnR!EBJcwZlL7A@72lp=~W+virT)iP46|Jv#cOTJy#1oZ&eC8%%a zZhxMfv4M57%Y!Q{Lc6rAuq?yQ>BVj)TFx6{l&eRNm!wf1Gi-CR)0X47Md6j!PvXuA zSF&DXR)WCy$LMF0SHRCoNT`oqwFa_sfVS4S6Rs?SI4HQojvo4k^*`0pw`srgGO4q^ zxeAZ3_Ebt(LD#YqY$Z5x(z>I^ssK6US*LM*2c&j{LH3GYxvfzFm?%>ptZVD+--ymM zSubv1Z4c6SA9A^1zp~Us8J6;a4aR(gP7B_hgFkJ!MXagCo{d<|i!B~xbW1aN&J&9F zy>gDfsX{GIJeOQ4l&6ySM$~3*p1l9rE}8ned`|2iFGTAKXSz3Lq|f`f zRB0S9C%xynlpB@iMH@v;5-yzh0yNjmOjpw^D3l`QHX44pkxouM%=xmU5~v1X?zkv%uHq9vMI{!P$h7W75c;=wU%~Q;Q^CnAeWPfU>*!K_2ryxBYhT~cA@t)$>xiMGd1u!>O=-#= z>oZaO$7&sU(i1~mmCDe}UV5pHsy<+YrOT@sS{PBEHeg|W2BtrtCgvgk_%QEBHTLfF z{Kw1h+>ixDRc!Y(U%I5opjuNg)Pl4mW)%H&$GXVZix1YwJX@bTF#A!3-DZt5-j}7f zVLHAMaA9Oj#@_+eVy5e_I!<*WG+Y|7G`+-i6j~eAP*>k^JmlMVl<$(_9VjejE`BsW zEC8P{g&*7EXGa)#C2pbRZg#z&mc+I5#;_|Mg>j@9e002fL*G=vc~pKR34(|T?!P*t z!C?}Y4}j;w;3|`h!t=S2vSoAlz59QpGJlD%F4R-Mrm;29SMe6t0 zwr3{`71dR3@im6gWqt*E+Z1dB9P(7L4x;|a)}I;h9gwY6<@T)K7Beo6d7D*}>4hS} zUK*_RoF!P3AQlJFqu#(`1CEIggTfN?j;58ZU42K#Z(n|A)opQN=p=aU+GQy{sq_4I z3;GNcWenPK?A5yKOHoN2!x8>s^Hk%emPetRzyf;ib1F5FgCB{Bt(_{S$lDFXO5W*= z>FDsUwuazpy#AZxv-uI=Z)y{^GZRX~8McN=CHfHCBVw4R#-5>DdtOiF-kAHbWL;7E zFwQ2bbz|hkYrZh9K-BCL>Zuw(<<;piUn8k}BU&lzKqeL5i*L0R9Egjai5iQmR{48K z8|VJzMN7amt^~(x>1P58Km?y0G#@?Hl!Tp;PW%v8-d$l)tuQAE1LiB+OekZSLoEsvsDi3PY6GSa$(~_RA<++cbd|)jk z4U@>GFiet?(o3CPFQ{4EsVHdfxh-rICEpP`lMhBSbZ+WA1%Em)Q?(dkm0NSkWu?sk}*^nFs+r!(r2m~x>+lCz};Q;`QEl6ujBe9CEyS|H%p&gVhmUjx*qI+5YI(V)r{rB{+1ynhbT;8ki8FA5h6YN z^^YIsx30glb@>C81OtocWgnK*3{4$kNC|b&S2PSaPL-db2`M8s|zG6^ZYxWoxBL`?U2dqpS%Sxv_8JTr2qSks7J6h|JZo;k%1e7JI@PV&d{( zArK656j+lQ-q_k4+?OMJMWdVdzgqO{qtm8Xz&y2mgZPd^^@0K1r3xPcj91*SBO9^M zX5DTB#2^>aU|+FH_LP^I=85`{-LaI(%A88Y^oK&9{M@QHV_)mZxR6yj+@VcRc1!BA zA*zil4Em}aHcd*`M_ht_QQxS1scmGAzNHLX2fNGgiF0lUa2nVsY693D66jfD#a#5P z+RekFsv${>#OyTxn3q>okx%x*Lu@Cyfn}1DNSpCgC>7er2i?6^JbCGanoDiTWOguz zF%sGnC2xMLOP5xoS(Aly8Zz+qo#ugd^p)noBFt7ZJtxiZI52^@$38;Xrl6n*0e{kI z>)!lKj5qa9H%&>7vBG^O17^$@rd_H?wxL4aarM?h`=(`aWv)UpCWv7$)R_M%i%)+M z1az0lH3E#(qY^q)3!Ur){}2J3BD+51-r<>#S(Y?B&d)o3b|@SKYJH*&=z_1mD^8-m7D~puV=YmZ zOG2j!Ma$2c>l?ij3r~=1%A8mC0ahH}!12jZ4tSeZysh$0KH*2fwDbcu=C@DfP)RFowaE`vus%*t4N-sv?4xYI4LqM7f`9OemrmS~m2eGc81SbhfESZDm(Xu2x4hif+?{qxVROC6_=zXXHC-d8CQ=rxPhNOwLQEG z;``%Q_^s>jy+^{HO*NH=zy`}b4#}BtxR)nut7<}kN0;QRGMSqPX=9XpEpJ1iNWnUC z>igNfc`Gv*6I@ybhQEdxXXI`BB{uT2##2rSd(#gi8*IbeU27{89Ki@aesiPN+p$dg zWZ8F?Ogy_v@b*ATl|ra$z9#(~?3tpkS~^nfM;|^KlAn5=Tooasm+^0_X{m01J~_V- z=80bBz2rwW-d1g_0Na4!CiYW`s`X9IM5{v(*z*vJkG%`BqmtJ??vlG9ASv*ZUO9P4 z7Fv^q0r(HBW$5`fWZiUWd@Y_#S~OjZwZ%w0v2w*!6DOtmdKugV1~%?-aDx&(mE91= zBIo*OPA*Vu_Q{P!2=`9F^8oG)gBsVj0+zUYFlV4)gV=ns22pAh`6}!?dT`$EF8It2 zXkFvGws~F$Ha%-C8^;UDuFB1T#;E_jBB+nmx&13FjaVt@@xe76H7P-Hr_s38w6fOycwGQ_7 zjtWmd8J%RYCTUYt+Gqr|;5fCwf;uOx)$se%F6t%}&;Sbb{K$0vmW~xw7cAm7?ZhMzfba4A9YD?Q%18V@*oBB~SiPp>=(1Vr4&Sc-m%v%Wa z#}C*AC&(Y=`;wyF(uO8$*VH&Gu(J<0^2$+y69-DscKs?t^2SeA`%@QVc0jEpjTL?? zW6Gs_dO{SvaGWgt>Id9(=xkf%Ndq6VE&5=w0RLCeZpg4p>c;SSG-?L~gZpK?afl$h z7_Ezf%U7I6`(}m}-rqV`RHxc)hkGO&2~IkCv0J$FI zA(5z?+B!G2&5_XoGNe>RxBSyYda~ENgG5hPM!6!!WB4i+4;cDvq-hP`Gh%{yDK|PJ z*@P&G5OWc+eq>1XDkCb)wXy=T?h#UOtf{WTT?Ub{Fo`L;2`RJm*11s|4Ln+`lO=gyt9B($O3{ zD1$ltE!6*)BPu_)tgDx+GTfu z?uGceIa)`agXeTVnywK!J!0@5!8z_QoYLru0@F&0!F2_wk3oiIFw zXyNKpS;d8QbBW_=iT<>x{C{*Ts1OBut))+Lv$;?exewzMouZopU=B_MEZnDVUKWbX zMGzC$o6+b5-rTnwe-P-ypm`^!)0bfoMPpoU8ANHR!EUg_^T!ww!PHSB*y1#AH508ls%dCU#l9~?v2T;R;;X!MfllR8Env0 z+vbXb$d%1ky&DM|(1r{2h}WRqXNRL{F}cGSw5Hv>1T&~Lbw6sMmbh&xY+5_1yPB=F zJhbZ3l^waMHuc0fy3&WR($G5=Tpsf1Mb5x&L&4O2Z+1-g`|mOE~0wMq_d z_e(=m491{fbx-VT@1lvbgR4)Z!aIT7Yy;=k&CFNg#36PofrGGfo|6IIK=yEiFRlae zbaY|kg-3J?^aPemju~_Zy$I>`qNruBpNfze+E9)xUB_wq)OU@}NrB)#CZl3dHPn z3+%EKi4N{JLiJM9nQv4~S%9s0y=kzqVA`slDXgef%{NWRE2B(qBjBj?&TEEkuW^|< zz1zz{Q{45HE7WbcnQoyhj=bF0-Gk4qxjT<*%;P2i*3i85l~dBy3$YXTdWA384Ju0o z)NZNwQo@(48OzaPm&O&G%i^S|&a8LJK%G~~I7>=H2CYTN=Bv8R4#9}FQp3`# z?GDg;R5DKJ?N+`(qdb`Rnm+8w!lDc`{)?Z#jIUz-y8sZ zufl4Ud4x({pYxj~VfoCdKlfV)^q&-*GzhZVVocucH2)s7i@M+&zKDB1&!EkS1LN(y zAI~qWlfN8)X;*OqvJq0T-nBj1cW0*O8kyT%sB$;Euqv6e1L86M_|upQWGnA6opHnK z(u=tWlLj+gid<9&wl+Ie;vGUSGtMbUx_&%QP}=W3dT|qllqwfq1 zI)G7Z1=G`X`QZHxQkkbZQBvva>EN~ucfK&gx5G%Qg9mA$u5)x2gUTw2I*sc8o;BhI zF~ei0dC}QMJ$Am78AlG-&=t=y3x1C1fDqD1t^#=RyvPw^It7gC!Ie-25nbn5ja|KI z?l(f5higrks>JOs4qX`H#>lDrIEGe)vI)Aj^g!4NhsT?pG;DqQ-F`d#&I^A2^&+54 z?W1mGyXKf@Z7mi!-d^4TEpSS=IqV1WHi-Wc*DmJbof-XWBoj-^>J_~0^GBVZ8h-aK z``%h3;DfK&Z4LmZ!-PE?<-RoKT6v_Oq|&w;u*ycC%0CW=6Pfl|@ENl9~v{)WRJQv?XEm`qo%SMZaV1v4HKhHWv*)rO|1V5W94h!~V_*`Ko_lL!y9?poj15dFUi&` zd`Z)##wRw0qsAanXOCO)rbRv$KBkjAU%>MO=$Q;BQpqNGKXNO-(CF3J^r3;E(bI>o zylC(LZp|#Jj`MeNNH3m%S;@2E*h+=u+DsCb>)?CoxasLjL-}En5=PW<%EB@n`j}49 zqbD*REEYGS)G^6-tJ{%)GG3C0tGn<%bGQsD&nUyHg#<`M)(#t!b zN8-3e>tLZ(le35&xy~p~JE_ZJ>z24a(?NLgZ+Uc&1Vb9OXDc)Us{=T*m_%+rR^V)3 zAH_h1ug^FGi>3DcK#!!%fj~j(WvYap7vRqA>`W$eVxy|2c&qjvS&kIv5lDg~7nN3q z9NS~WBN}ZJoRtyw1*bs6Pkt(=t9ZtTiyxMzhPh!%b7^p6k;<8&HH$9q&Z61Tu&4!a z@D6DCQ`yG%E&cfiOiq`g@}7?xpiiZ%cX}wMPj)JYi8dY9w$%x1F|&tB=iKs)Q-?b_ z-Uvz1*E$dky45QhZPbC)9n9%#)M+}Z*&qyAooClONaA`cy1^C%% zJTKQmSXtBW7gdU&(BXd+XRNvqKAQW~akA_H&}m{>v>D9G@JPW$^1FLly(Y zJb&?33f5kxs89zUN*qdD4<8J-8J#3BW)gx>H<#BKU9cWTai73G-o%SO?bP}G4^d$c z8(SWQ#n~DdccxZ`z4D2qTw&hKsl1VjQyn-2UP@C7Pb+RLE>O44_9+UUzc+ClVW=m5 zb5+2PLj(g=8dJz9PWgXWd-JfguC;I2KIgPOO*1^!DTBu6=!r&+T7oza=V?Vx8_`-s z;((rD#E20URGd9+nm9{SV+5Q|ut5dI8bm}iHc=r_&?>R76lC?=l!}V(KwW!Zt#CUvS zjIoxH?!Bs)9LY1f;J^-jy&X2fK!YA+?$6J$SYXwe8aiQl7AfU^=mg=mfVfQm6OtEj)8-qYb5>Nj{pjFf?_Qn^Q&l_h zVli>=4uLRMJTbo$bU*5gUhzT|E~i&k89}B~s>HiMRJtyXOycX7&I{WH8sQ<(3B%YQ z>91y04<&YBP-dQXH=Kf>9OsnhWVy}BIxy1K?I8-oG01X!&56LJ?)|7BxSgqk20_9J zHmZ}|kR4l#*Q}cLZU(cn^BcZcx)v74<%36g`jef@uljUuZpBNB#REwH+puQ2b+7FSK2g!*?ujb-ow2hiDadmX{w4U^lR%K1y9l z@)HvW5^)G!TgIc1551&N9WUWx-8Lj$-ZwO}2-o~3?Np7*EIURuL%Y1@8BDk5e=>XA z;xTAwl&sS(s5*cpWAmgA65a8G9a%`N|ifzGT-Y%31l`Fe`3WfvSX zJu7j~SaKEH*7Ct%2gqwAA*!c$C?&pE{EUx>48=36ANLSb%Xy`;C|$Pxxi$`BgjMnV zI3w}$b~Nlixo%zlMPc4+5x)I5(PRlE@b;2z>@*eJT-)xq9vh~_@<^nUY39P)RX_gx z6#xG8du9>;V0d6c<$7cHD<9TQ@nS4W8@4V+=M19jyy4?l!S%z5ZhL+avjLLO`ZTl4 zRJ2LNS>|@cBI*4rVJ~y*CKnsr+DiHVgtOU17k_kI=NpX=8~r41&_r{Qht@McccH%R zAEUg0^|4L7K{O#CdN$dKj96kI%Ny!6G=Ol*Pk&O=Ju8h{OlqGS)tOPNyJ5Zve-`4fnH9`%2A#q* zPMGYCzZhn%!d5D_aZ*tJq{J#U>LGwpIruDF9KbwnUy5)~!ba&@kG^C_8K2*&%TL_} z&<4kO4^ZqBDUhDB3gqLkG)3!TIU0>I(L_M~5H?6Hovta6vzFHO^CCW0nO7Xp4)Y87 zIUr6ZlV6kH62;fM+CzN*@yj4wNc4`HyNF`ARK>p#aP&;JrxDMJ?jRO(BUL+pSHM$0 z!UGW0T-f~-C9DTRN(C}b+w-7AQFo(X91*dDJ|R<;C+=J47ZH#yERLD|`=<}Gb^ z6jxjLuI{v1wZ2K~{JU{)G^|x!6O&^^LIa{y5;-l|Nw4c}Gv2;Ef4LKkHO?h7$Kc%@ z=d*1wN6{CGODS!|9m=-eJSaGE?h>+Isd{Kx%W9!+S}G7YX?|*)K8R=@%WU`a8E1p& zPe#|U<>!?SRvYo6xf}{*G*6;MemRVMPtyba%BLK8u}{>kxwkCTewaF6_1}DN&vr`- zC;n({d1W@k)zq7n33tGlTo#frJGygZ@>u1Ej{W1rQ%s{cJLfCy9p=@?% z+Q`C8Jsy2OcV+I0o$+~GAglBNVh7 zI=7UT>@>-*m8|-$tUbJs%!Bu+)nmP7Mqw0UbbO9#cMe^-SP%#B`PBEBpUT?0gK{2= z;?gc@?uMOaKkZ3O9U7V;9nSWTLqD1;e*$RUpX;zjfHtjP*hCE=3Xu(2-UVUU+$4JD zxynPEuz>25-G^;%>`nh_JMY-FtW+}qkrT1fJ0&JL{p}{}<6jNqJDIKBcCof~Ex9!2 z!>6x2y&ues2)hOJhC#gDmD@dUG#r=#M~JxvpJy=_+p9eJRSq6#SBlsdBF!4`f@*HZ zx&3vIW3!Bsty=9#wCth=)*^?o>&nRCxx6@}#3oqa4EA~T&+9S$KmPg;|Lx`XOxV72 zKZIW1JGbIWp*n=ltS}`FgJ6d{$sCLs-R*;XuXB`2VeZ8Z2Gte=pa=!7IXTZWM*N1; zE2<_fnW@($-ww>pdin#g)*LmWP6{S1i6Fywp8&1?4|c+6bHSc&EVrEfenJbZbG)`03sKAQ5JTJ-_xkHXxVY4S$bjMmgWiRaKx zxxC+(am%4i;N#16jUKnN-~^{5d_nd}Gy0~_U#=Z_-fu+l%Qv`?)rrR$txSH%!2Oi= z@sdrP)T0}+8riEd$r?U$fMj+vzxZn%gYF0>F zo6o)Q#IZGjU~9bk(&T$N_vIGXp>F5gJ+ArDoVCvJwYx$_*Wnq`)a%JoZ)>vyt#aO{ zfv)bez1$z7z6d%iDI+RWfxs(5n`F@iuBFvN`$o!-aM*-mD4S)2IGTN>V z@`z=;&{E5Ed8qsc?)h4d9t%By^0)g?7t1iCuX>Lc!}5=!5F+6zq@@HF*wrQa`~$AP zkW+o4KJ17SPD6}s?)3QRT5gqdw2EX0nUW*n#hNVZ(55E*ois07KhQY{Ad}8 zE=%wnT&&Q~{$==`xt*4ue{J_kT#Q`svJqu>WfoviiB)!H<&iwBvbC0nYmHb0fj-XN z$#W=$gl}oX@!jzv1k6_l?&qWL@yM=(wDm zjce}XYM3nSd^w-I9_zr2nx1C#DKH|;02t~woYHr5qytm8+!o3=H4=EZd$d6$UmvXRMs*X!7@e=>xR>G2sMDD{4 z@q=gVuIj8@XT=G3M;#k`gR6y_P%W36qoYq9=aQ(+E^}*UIKVPRrz!)L2}s?=L0SG$ zeqNyC4hV2A1e@#4ZX=Ic} zo?%^glUwZ*O~vv8tG>-!O&fDf{i6{^6&%ssm!mv?{kRfd))KEk_3c4rNrplsYgVi{ z#ix&PPo8C3e3YjHvl;Iw>V!HtCGfZW(p!4CXvQ0jKa~XWU1nAjcaiId{`l-yv{y=PmvA9>&qFTh`Tdh*ErM0gcEK6K!h!z?ex);0)xw8-!>_A9ZAILU@u!65ebL)LnsQg@ z=q?1~8bR9q6T%^*gltNuu-quqK%bikfd<`qYkXY|!x5jhtPPe|>?X7b4VP{D$1xLt zk5)p=B?ED9zV_lK(gi;7__}_3NM-)|>#^#9W8)Cg+Nao$+f&lO06%I?`{{C@!A^{e zvumQypWVW##`}R^hX3@N8FWNhwD%l^HfNG5wLOP(<*wES@Psjo};ZedbEj~F#u&1^f+DORrVF|jh#$=ddU0?vU=n@bCwAc5tqz1^b_i>GhmXu z|#!q-X8-W0;E0m=rwWLJ`We|OBSA#e{hWq}6H53o3KA(aMebG+Im|fJq{tbDIH%)z7cA5Dss~qau z!`pzbJlPOJP?V{J_D2)8Z#4SO(~%Cm($3I>71mJdH9((i_#rC{Qj$~oE_Kz{E9^(! zsOt?ybf=YV`D&TkdonWDXmwOH!nNJcVHp`I&Xq*cyX#ViP49_-p!9jAI)^^#<%~e1 zYv`37eg{p`IVtk=S>H|@RHfk$tQF6Dfw0%Brs+*$$M)B<$-8pPS9FsIhsUyfskTJl zUe5ONKzDV;O7$$=;hhD+XF2gFuKzkZ3|u?=f_r-jINN%O(U42)606KYa?ajw7>~wH za0)W4f&?9j#-FuEDCOCd!4|xexFz**`}U27+1v6CO-((tN#3ie?z;dipX|QAB8GeQ z8vvP8Hov`hGBwV7b8#1ZE;mE5Vv@HzXHS=@2(U2|``q(5pFMLA zp($*p&WZ$=M?=qt_6T?_oA-8_x4b8DxfVQKR^X2laf>W-a#>x_-ZHzO8>BtXO;pw~ z*)F<&zF)jXxKur7CgH07T>4YOdB$iiOUUUNM&`<~6Somr25=h*6eJ8x{Q~LiTi)mvK89>lo+r32>N~sfKT67Ib9{~ND{{;4%wJ{Iv(~m>3Jpi3hT&sV z=)E29{#5F#IQ7`8V_xc2h5w0v0-k7v*fFiL&t?(8tX#0n++k2}he;%E#h`8VsHeL- ziZ?@zKETzwOl+lY$jwv661_8eEIpi+AfuIE$DxLSzf?!2kfXWhy#fK4LZ!e}x!=`<)AIy011<jz5mA8>2&xjkqHKzB;Ot1KNd} z$MFAD$``f=jI}HcMR8tY_$}%8gAi%K|M<0$q57nGlt6!%=dYV7|i zzG9~-ikgVt4Z&d+X*@21r}WOu zipKX}MNbTKqK~A_13GZ1gMZz0#7OSq6e|v|43jltjz@=ZH=(3CWWymgqY)9|M3Y>l zP&qE$eVFZ2U)BmTF4nhk2(Nc}f%BuoUU6_J7}~PbQ`Sc`t(pZRu;sD{HlNKzIKj_- z-u#4UK6Bjcu`122TA*1TGo4{~zI7m3pWm}0oC@YvI@q#OJwcgwv%2-_lW?h@1G`!Ks&8kL6Ox2BKg&Y_LZ)4LPkXJbDl&8yARoO z;WqV1G*l{dV8~agW`k&b|mo&t=DjS)#hU{-reR`0MTC2VQlB3l<}5pMFvn`$i)< zfYe;)cK1TY!lWHQn3x+Uslw+nmb{0SBRNUiM;)uTB8h5S;#yF3H*?;tDcFy@>i&N` zx&|gF-aV~eWS~`_*6@{I|9t2DJTG_8#r-!LQNn`*(p=5XYORZ*cx@g@dN=S=j>>}l z7MJncJx=o<&@c{E$U^mly;5oh0j66++JwIJM=AP5t4BipHCqqY$w<=uL?S4-yIsjS z)WKmMJ2#1QNK<$V$Y=lf?LPnJ`g@|1-Xm_T#P&PG-2x8Yr&wheN)XqZx)ygNLHOq{ zJ)f#88ZLp4%=20Ur*SbZ9fQ@W;H_QN73Ts?>c0Zr#NGtx1$Dols4KPbN|et|#&VNH zmAUNQej1q(IX2NA=r%Q47=`HVSY^}~Zf>6w7#jpx=sO47)fA@1CU3p3QUN<&@t*Z= z@5?>MM6T!OOd(y-LHP8;w!d|@ol0~L>?5jR(od`6{;BbYgZc5NP+9VLg`F#MK9yV? zom`5$=w`W=*iqD5QrFFwJd06|y!1` zauca@w22gt4@=t^7bG^8~C_(Z)g2m;S{59==IiOnVr=0@_ol9qTFS!?Fiq~VD@ z&_83o@eboDU-w|Sb0%TleJit&%g2G2`N+;WjN=ea;NoBVgrF-FktHRKxR#*DRC}Yn zjTi}P0=o(q!pcO+65|hzvzBp6?O>AN&!_yC=;Vg4qosv_s1pKmLuexfWR3tT?{kXW zU_@>yd1XGxEj6aTz}n0d@OPKFep6EgH3f_`1iS$Jx>(Zr|ICZNAL8()ic`zx`DwY0 zHHs_d*5OcQQ2|ydQfXquL>h$)9lwtQv_Ntg##IXf#xaBNO0mSsZbxJ@%mlV{Ggb>F zdxo4Jb}sulXlmxmz=U2uB7pG@pf2um?Gk0a-XIQ0Pw6i5)EDZV`HJ=g`3ue(m;@^a zt~rdtsD^d!OO>Ew90d$hbM;n4ziiC7kXUK#b`wQGxrp7nFANj5GkLAW&Kzhh$v+tV z)0aQ~n<@0KU;VCrOxdR0eh}9sECVDO4Ja~HH>>@+-e{y<4^zxwm_U?8?H0|1ADMf$ zMiqH%#T0rp{3JiInmG;!ztMO;K>4uc*H-Bsbu@I`rg|4SHOtPc1Lpz?GWkf*(}BV{ ztlDr76Sb$q$0@NRYkGfWsv+O0CAa22$eH9asuul~Em8xm^Ai8?MS6U>DgDwOzEM55 zXuq+GLGEcADBw24yoLe48=(4#%qE$iW5h=QS;pHtySmKvZ*&M?GjH#B_0K@2!}~oK zH3yDsYAeTq;12-UdCYGqs8|O8JMRm_Bb404K$xEd1p(3`Z+#-*2Lbbwo`=09QD|vN zKA>St&%P$3e8FDbexhz)=Z|{04Oz+8VqX;WhF9Aa5t+WojXhVy3ucsxfBwT9mCooEhp*Wb8-cHF@v9U^!&wJYIf2Wsqka^~&2T7%wFH0}gFps0& zbE&382hyp=g{znN#M%y6YZ!pS;glDp)MNNf?NhjweUpgpyE;i}I$i;+w1iYI4zT}G zwC}r45!G=oLi=zb>%(fCdMNYlz7(E8&X4FAD%bC4B-;Vi#W$Ml)!Oec_#w{ zb$~3_&&TP~hsoR|h0}pvBBL=0Y$Q83a$x@|xaz93hNnHkyC0*B80EMnCeg&p@Zfj^ z*xWX`}0)etfa99eB!)c{Av)yjTp(A z4V0{gJ`)HSSHdKr+t=ASb{1q3uUK+*I`u+(dVhKS`z}#Q@hO?>2cvlbE!Bp653KWe zu_$gh18uy0YdY)H2x=4OrNpgPg(Yu0X0DIrcjs?==E_qhQk^(ArbI!chUa%$j>6i; zh0Hj`N}UyUj9yF6FJSCYpR@pBjm8iIy@v7j@?fw2jg6Ra6wP?A8~jth#7Qn&V*4!y zNu$7zq6q33C-(tdgZ{Ya#tMV0^-O2jcDh#8XnC(>61UukFEC82o7;;CWyIz=-o4l( zBOLW%LA|KzZr4Fd1=Fz|vkMsq_@=Du9hlbo5I{899}rD;2e?~&TninLOZJNdfwVqq zQldB+GP^n{ED@*)f_x-Ru^1eqo-hAw73%-QU~TPQu{X$WQNs2+ z^f#x^&yXvS8GWdK{Mur!;*G}M?#nkCrOFQF!(jP=htng5@W>9VqRL8$@T??9fb%ZE zhmJ+NI266n_yPzf2CfDq#HHJ}G$8yNHTS)1h!@^yjHUpj#ApE7crPL)euY@v0KaFr zmNx9Nr1;xX*`EN?BhTADd2*JTK&hPFRKObs?RSq_X=cYpZi`_9ZbJ>88*aqUsT1ch zg`3_t$sCTR=sHe#mH^^E37hT(IpDC(1|xuuFy9;AMP1On9E@ zu>Syvj5=DPs&XAoua~s&{Dn`LC+cQ^H5&-klQ1>Z#HwfZVft|&FYUoIh@Ell_D8Lb zg)x7o2(w0m$@C7If+vvmWcn*0aM!b#`x`?0&BEfNxISXod#@xm_y$|}k+SM*3sys8 zUI|)?v|YItFCD;kmm+aeu-#QR?X8qjvT}yB9EP{28K0onv}KCpW=pe<$ZiA1Xipx#iZ(LV2;eY$eQrS$GuP92r>q&AqN04Q#) z{0Ak1zo~}yEK;hk?Gx^!>hgBnW5dFR%kkM1RWbbgHYqmWq&02K_@aJdO`*Q)kvmND za9F8W+(2&;cqc2i$IIpkV{@LN)gM%T%?I6=UMA^vhB7X9WZh%xVFejVaNhMXVtZ9a zcfPEI@Jt3uYB6uWQxs)XicE@RG4-Zp2tZChn_*u+tUc_t?d48m$l~1*e#smUkaL`0 zT$q>eUEgUkC1?HfqM;^(7$SIQG3eO?(JaS;Fdx`~1HfhycCh=D>j#A&%;b;*ogx4A z%cwJrR#nV2&1PCh_4ST#CSfMzT^;!DgfRv>ij9w!`b@BcCHj7ddPoN7%BgFgOCV*B z0}O(!Bf#GQgv=&53hes9D8mFq`s0T1s|Kk}fkp63WObB7)Va31aq~g#P`k^OEb4Ta zp*D~+J#Sh0-Idkm`l+oB>*%&_bZ#WaBcaq!sj6_PVVRh5{Ov|*atL`b?`-kkp<^9` z!R6ry*=ogH7QSwhc%B~P(GG@qhhPW}KuTkSortAknYmG84QN+|<~L@la&fS1xj4d! zDcx#4HG#>{#Sl^w2-9WtOZ4cOZb$&nLzF(2bn*Vnv|15c2OkQChh3dNbNZRcPh7Uh zNSarU@JU$Q&DmgbHcjAC_kuS!SMesXF{Vb|qoSc@Osg+!+(x(6PA_ zh*O1lU+Uo7n)PH?8Na7ksLo~Da!&@X-9WYDjQ4zDFByPHSvI*ii_f3pUlVUQbm@^lVM8f14__>2p+-`B;g#56Ci(UCKDpq!|xM*e$hWAp0E(ix+eG2a)^G|YJllvAnBEdP;wV%I(mn*5=SPvNZ&)#u64lXDTh0oQ&tUjO#; zyBggOK;G6b{D$406m*tMPo%pEM66??`S6zNZ0cV!_s0&XwbE&~Pv-nH#_f!I&lQGW z2p$`B>w@TOX)mcbpl#ShbJ)eu(PV@1zqkG4m)5=${R4m{PYgX82RtzV-@5k%Btv;S zr(}ffL0VVW=1(0Bn5wOd)C19`)GBYZ+_fTcMLoH=I0)$TNq%}Z@qrL;Q+afkBpaL{ z^1b_$4_9K3dbW=_UNr`I9uN*4z9Ky1dQ{}y+OKDZ0BL>?7d9ynnumQwb->p9v}mTR zV#hTCOzda_>G2z1#nus1wbf(Hg55;QWf$_C3D(! zi2f>Cr*E-x7t2k6<6C%cu_gQ!?y6TwR9yIaAd#f5}xz|$aKQ5 zZxpQp8KX_<5rZ3Yy`u&1dZ3Mf)v1GD@N){j?}{Daa{-ASS=~iT4Bx(ZI(?KfL%9Y$ zGQos>z=&f+KBq9!65PA}R}qK9^%Lavzw>QE)ZAX{Z>v6;ypRl=lTpL|)VuUhZNxA> z$R7ex*+~CLJ^)#{YyML9G-Q@HCATeeyT2PmaNDN~z5Y(n(S51_?LA3k>YC)2O1u_h zE?l^{S{X!f(X#f1G4H}(Y8zsNMfq(#t z3B}=C8^Hv#>>*~-a09}lQODiq)2@*Q?5(Ok7FiJayoP0brrmZa7YFxQ7HfK`^F^^O z;`P;-HiMqt#EQq??U&1$_wskWTM5bLi_0EpkrW1UwRqasAib51_!;2`ygsLbl3DLt zqqp*JiK-KRRDnqTvNlObBIJ70z%vhhl&YCs_j=&G&j>dq4-%=WVL);khGg2h!fPDo+zN>>xosZE&_SoBzEp9=jPtmrsoMf$EBXR zMP|J+`nIxvyAkht_h>FHO6(je?(CT@7@F5Rl%E2Xu#tFlE?O}W9HkvZGs&cNm!&zy z3)loBOaj68+cg5=DT>^?W>w&GP}e00a)LR#M%(R2PgI^l$f-<5k{M0EA!1M3vQbE( z_N-yDJP+!I`h0xpl{)9BX#C#%nNv%b84=G}GS{n<8+8JlIK78!!&gs>qcjoi~ERhk){lN!k|=~ zFk#d%o_>&<6qK&_wEm~Q`Pug!`)aTx%&Fw=C z4V%;^>79)bt0GAU;U2Vy^G0Lhwkm<|C-KT~oDKI9sn35xXS+O_L>*S$8!F`wEgZ5i zYzUHcTwc>zOqd*(IOVXh*z5-1VLFhcWBLU$02j$*H3XG}q67jOuTs5gJ4vwW=rImBI^TBGT_^Vef$dXM>V2e% z(xk8*8BLU) ze7oTGc)9WRy-GfBY$Dn`bEN*jz&c$sD@S=oU@e0Db?4M(~f4Tl=~3`=I2kG{YaTqkndw_|5)w|R%AC+mFy+>=Lm{rcNB zzSBc6HQubY3!*yQx!Kvzvz-<@+=nHN7wDof4UIAQE|wtS?3Uj$qJ_7+4EVH^&-Bbk z^Ac_#TFTJo*$ktUS6wq~WG*9?;jDGKKi;WxGWxlwFDgFa%FyRn)wZv{Eh}B`_Am!K zoAfcD%YAP6=>7hqO4Y$1$`}*7%f3GEUm=u(DS0q{;!-yIZe;|G=koWCzq;ED7me7DV|I$TBfa7uyLzi_`z(7o!Bt;hwOq!r zthwEfAxQ0RUwwoZcHYJ%(?@=#UbI@OBM8acvrHc7IyETBuos3|k5snGwg3FZ&Kj*5Wj z)V}2>lVt_W*q$Hx!Eo5*jEs?&th>mt$aC;dD0T2RsafRWsWr@v9PCcImgn#$`~J|A zW^lk~#}}I5fD3!iW=4zcmt-pW&v+?92|Qf%ow|&%UEg>&4OVGAmN8rxyhv^I~ zLb?}Jv#NxXSBH_J#rygF&qFApS;qQGx1KGN1M!jbwD4{AX3u;PveVVATMmUw32pNG zUS6V`?6dRnOCA1}DCXEm&bk!43exv`+VR>ubnddMtUxsM1dq+8CZ5_lIyHvPgNKeb z;-z_rxmhWwJ~ZaSeskn-o=H3<4e%t#2U-D7($Iq+Vdir&_0Jq~&kfQk;^nD!boOyF zqqRD>P)mI)CUK$v$(rYXcSM-_Tzo`VM&Pd}tOjQ9*R{L4v|8Pm>Ad^nnm)gMMbAjS z);xQoJ7E~g+=1G~Zkls7_l&h%yOFU^rsb^&TNYBg-+jBT;Qi#i)Re0o=lBP`=a?YP z!fsy(oTQC(pF;5}b>by%pA5>xN?G~ZOOHoXg0j zl5X*tk|J6r{Lh#?c-DZ@g0~~homw-xB_Sdc_ONA%b&DKKwSMMCv2U=%4W?Jqan|P) z7_9%L_~CTLucPYUUVc~B%;|{I*lI9_|4fCOk#Q1bO0=nNUfcD6b^@y`wLhY{gF5$4gg(p;cWjVza9?+Y~oZB2~fOnEg6LX^q4bQwA92ryp z?Ju45p(Qn5Zd-fAy>bqyTT%~k+>XALJeHPx4Zx%h;-Z6`CLbfFUp}zP`yK|%%NhEFIJfB5OCo;pmq0K! z`cBnsMn)lNGOrgAsg7TdZj#igef3d>@~>ZI$oGyzznlMrDhvE!tt6#pL(#OTy5gSs z;b-2wzy8ulqW-bre)GviP&fMqLLvb}P%kN!Ye#O-FqzMLcgw1nOn^VUHS<+^W_IM5 z{MdC~U;59R5r&4Pcvt!Rr`ZoK|7$>;R^2)RbtEWx!{p5!JIn!z6Ib$3s@D2s?GdkqTw7B;iF_r1&PYjI%(QqPD%*O+bK! z2tOD>|H2ncB8ew{AwM~PJA6epxc434g|()??7SgmiU0xg6&36P@*FPi643* zYU#$$0MV*hxIV#&$nw$lup*A`0Spt{rJk6?yPjXymlZySfu8 zn_!E5nELDcD31w63=|nf&%Xtfiw#dSia5JVGzUb^9-UEKSq^C1Y9AA@O36X6-V5)Y z^7C%$vl13W-R3$9p>Wi7iJvGw>WX~cX8GXsYSS|BMdufN10ncTX9B?iU)MFW1MP{N z%_vxlK~y6q>n5MP?p4t8v?T`wD9El}IN*(D4j6vw8qo98zux-4`S5#YfSs>4)?>nM zqC=|-1&F+s;o8GEAjZN(nhmcH<He(4CGv1*tky0X{pbmq(Z(GAkYu|b00{^p+Xy5A&@6Lg`!8adssm|x+4vRodP zMcMmXHhHiOm$Ud*Sw?W!KV#+DxI~$4kXB>JMw&TfdPupVlWZsD3qzU-xcRd zdIvxL-yZ7!_3`(lbN_+D_E~qhckCD$LE!rQaXnIt__Z3w)TKKb<>|$3#e^_7codx5 z*;|(xSA&Fj(RyINVp{3!d~w{Gja_@ckPlI;lXP#dmM^nd*FNEEKm>8U$NiI)fnNDc0M)b+}2>_up` z5YuvWb$mhRb`;Niqw}PUqO$t3V?HT+u3*skdUl!HlDc6p!+Ixk=xD+%K?kV%NcU1B z(uP@|^sMcEyhaS1-fNDyv)CI@a`}f>>u(V%*%HM?+=7I3b96r7C@FfzPx7%FI-a9lT;5Dr$P1=ql;vm?-XC)%m|ZPl2IQ0;rjLla zJPrI8a%)At7abZbQA?2|5!JNt&z^f5)e%=#1;|XBT$A>gMDWng2_ukzukGE3AAWyf z7>#8#oYvjH6ph|Fr+Z~KXj(;72Jzj2y3)aLJeg(GYCg!Y$gUs*U-#6x3q1E=jvoRG zNYLd7-YSNfc53MZVEW)d%;TaVn~A6O)x&yM&oQ-u0=#huXb-y8^dTiDr5*f_pL_Eo zkZG31`G)G#P|8r|DJ%G80*F(>^C`@2`>ua*c{imZq~$fa1DDY=HAoPuYh{j@24kS$ z7gBAI1IW!6r_1z}L?XR^2{qS|sLS2|I^St>DD0&3QOEnk4}O>(a-;&WyN9FafjDuy zmg;~IU7mSnJjBw`b2n;m-c{yd#o#sg5B z(jLCocDixk=5}FSyoJ>YP?7kR@J=#eK=oKQnGlp*KEk_qA*h`0Gp;O45r6q#Kmi#Z zZzKq3;*l{!$@}W7EI>Ihna9&@j04>cDzqqhay4ccC(iSO1Eo#;+&!hMGJZ)^UScg3 za0}eMM904RS09Q?NAnsZtf2@wBQ_T(X;NEBGd@@0xz!hX^y2pTQ`B+8bcXjFP!srM z)Bq{dspR8ooI*cnOa!az!^v)OzrtbSsaP&_IRReRoqkHu{vUwyl-OgZuAP_Dc@EH` za`x5Ibn^xXsM?)RUgB=5%n2!Tc)>MOKd%i5o|nBopoQWiKL)BvaZ{MDeB@0>+?GtS zw@PO?qU7mcW`lIRo36`=s6FytP<;O&shtgXLM6cW`%k*7|5Jsyz(q%rS%4;>zWWXR z4fo?BOIck?bT0?-NRS>Py-stHK^3dZJmx@xCh-m6S6 zmZHqI2_*6OA19bpSG!SO-suU+p51BBiM4U6@hnDShSUHWn?4|&i8Ee$P4eEJdd z+==PR8>pa=vt#s#<4d-eNvgo=?vF+SC;Ddh?wN7;0Ahb|YbVZ^F*|4JwSyHYFs+sV zhMQqRIW*|7II8L?lVl{SkekiXOQl7a)lSjbjpo&dn?NR^7YfGjUOH+w4q#(}7b{roWEj=#*;&Ni0 z0ly4D^h)%%Ebzm|vfhavObvZ55KzlOfja?e4Ghqo1ZwP(D<2`7gIO}gu}s z^@HdZJ;#u6M&-%NPiF~sdnpOHg&oH^0-s-6d5`*ZM`i~G6U~AX+AAP|4JXk#^jy%o zmBCdY?)UacA7|cEw6?lM$4e#K3k=ih?PQj14M5|fHwKS)Kwr$zTPmNRkS>`^<^6#z z)n2Et5^u9zk(=!MbcnyK-`izkQGWWvqtVjdyrWOLJkuPz+;3b6z@1T(tY#ku0-)J* z74LiYsOE?F@D&E3J7HdJf$^l_E%hv6?TyCLTWB84;!a0BeDSB-{f9wc{7VN%!ywaE zs8r-~3M=5Fi`k?tv>D%xze8nE)ZJmS>UV>l0a@UeZ1%3|(&(fMXodUv{AnRd~Gg^mpsy|oyaIrPqm$TV_C0aKEOm?VB=>QO&oRxi>DEv+j zlU2zJDbuYbQ_3+^toX$&E`kNK5y3HJ0b@RwJOwOye=0LRJ58bFMlxzt7o2=y@O!|7 z^Evd^oFU)j6T6(noA<&CZyf+tFw4pUt%F1(PcM$LahcEB0REXc_n^r1VypSw@!>0< z{bDI{<1z~eRCL0&(8|ljKGxrI*T-XGm{!3%dp{R&QD7}(izjnK^D6y1)6GYo|*LUhtC=4S33gAb) z3a&8Y_(?H5(j?UPClye+NO#pk+QP&d#l2?U=>u>R42###EohM}3 z`%!Da@@PQA*Gkrj&Ar8Gs`UuVoRdPy4?D3y$&Ul%Q%cNoGBan*7rNw|o5^<`Fo5NP zyF7s;MnAMdXpeE)BD+IEthE>S4lyPv_UP!b{0~Q|n{Y0b(qQT~Z(@uE0?|(2xFg*6 zt-9PLu7{@gHW#Xib`~9cojJP?ElYk%mD8k1t4JGZBz5 zO1-r(4Of-0PkL3u~P3bS#7%IBes8+;HQBW^0=B$g~ z9~EaRtfby{re8-Kaz<#|A`hF<{cO_Hqc<9IrJkK}(ssqcrj2xc^hA|0qZG( zlDS*kj$uTx-{RH)jb7dEij|2_i; z@FTaOE^ds*Rl*&9xof5N9I)QNlM^$ubJ0$6X;gmN5*|R7(0YaI=viHS5n!EI)}Fl= z9z-1nm!MgO&)qh>QWDgo67@G=HIN2@`!o<9d)+jS=xE#C;o>KbDS5GjMOLxDyfA}G zJ0O8fEpTOb;vBKZ3CyE!>r%O|MQf)F-)I<#ea3Z%OVVDw(clwZ=SlHT ze0QU_Te{1t{`wA7G$CxlXIZZv^>>8Bvzl%v>Fu3VZGA7*y$!DmV!Mmoh$hD=K*cxH zTtJM3?uhv)fP+#sja987+_ozj_KJcs@z~37hB!osF0(g2iNT<$k@*4^i=@;;2^D{M2rCBAZd?QuUF{S9?Gd#B%B9^Jdp2 ziCCVkSzbJ}$6_>sZ8Od}pXi^-9N44V+b+Ep8tR8>Zii_KwFf{skHst99fcjBf)PQP z=^acG-8F~AKkL47AcaDtC{PE)@}EFQoymI$lm<*>4M2N=i6aAM(A&ur2M906BeY;Z z70*SGi`JPUO<-B{u<13npo(u2{I_@(C0FKePy9UDfIRR5`DcMu0Gm&xnFBgdCX{SI ze1I?>@|P6{8>Jrt5#$<%TV&qEf@vKp0I0D|8Y8nD6;pKM$f3Gbq zY4_JE7zYRUE6oJOs}X+vx9r%*eK9&c%pqwH*GWXeZO3bCzKLcA{y#7Z+2d$j=_8u|B1@8@U;#u|2i>XTaKvhwh;2<5V@x zC8gDGHKe>k4dm5Q6KX3PPAWivNQt*e;mhE^{?Z61Ty&25eKW-O?mpJjT^R|nV{D0A ziPz$BkC6ZL)U~9AbpbymW9(LlY(wDG!7Fi2ZA7~P#-_ciPVte(NxjOtOyL`izW~MI zb|iW{LP>PcfpPhYC@!F{Oux2!R+eWDV9`!1^6RJbC?(NIExpTK)m0uDAszP`L(ukC z(mBZy_{D2Z-Z8uV9H6pluc-62Ylsz|yz!01Sw9XMAH>R04Q-XiX(C$4a`NfzpIe<4 z+~@(*xz0LW~S z$-dFC8&CP#nTld?s166yX%YHQqXwYnvuPN=8PxTS56@@yHy&{041iPq$1i*dmt6*|J$3Scio!em|sONO|< z<4Ggn%N>O}s8O3v?@xN;wf1YDDZ+;J)h#6lHjin=;byiS@-IJ;Fjp;h)k!e6a*XTi z^M<(1{b4<|J{3{+=5I=SHqZ4$2YK22j-;YSeyv6|@$P$Hl)8+Gb_;3_;e{SYx$m23 zQmD`_1n4|={~|uGb1{+!(K4ytc6`kj@Inp!>*=B=@HY!6qX4-elrz>S-_)mgP_kW(DDFFH;-gX5j(fT*e^-K2(^1AAzvyo>X|Jlq z@v}RncWFM@Pe&X|>+Bk{5`%>bWZyiTeIijqs{MMs&rI3Gl((^(Ppp3Svh(ue4GU0%w^jqa`$LJ-o(Oyiw~geb7G_V#%$gooY)Y%1(tzls463EyI>pW8S>(()1wt0>@~p z{3|t+G5wu!Ygi_nt)6=b!=&02Z({lQo{5GooAk=Hob2crlEDLqWa$BkV(Edjq7mfmH(R(~ryUyqSu| zo;_5cxxZ#Az)cls$+hkAsy_ylKt2B(&yplZFb>^42__AaL z1x?m7p9!zI;{QW(8zYRlq#f~@v~B6ww>m4*!^?ZZSj?=&O=ED^Yq7@1^iv!uisVgM za(KFqT76N0r-X*1=FMViuI$*J$Y=o$eRNQV&)lJbYA&|o<*U-O+g!J#o@%HKXsOV1 zwWHdKZ_F{io7`xS-lU%u6B!$uYax()R#5)ti*~!Uwo3CGglCx}3!lzrHmr%c{1VR$>ZyD9O^_y zI}Gy=yiPkRojq+sj8eKy_m^tWRdxYgeD-#W6> z6h^PzsLuJN;1Elv_<%eOTjy!+SuOra7oysB28w>g8uIRW+4zbXnO|EsFr|T$vpGdy zv*Jda0=5jWUp)i6=^=OL0|y%ROm=C%v3z3OiamCR*X(b&d!RW>x2^e7ZK@|NmPmIN z%4Oqg2-v-ry0zVMR(chUQK-FR7h{R1`T zxGdxO?f1wdpw-9UBgd=BBP4R4L0Y>Phs?fmmn7gdwuem-88C2R$&h6Xa!3(DI})+g zZzEn_xCwJ{r8u6j5Ym+G{JgQLbX6+P+SPbUR9#|n8- zbf=&ATqaLnb#4*zAUIUeDVt_V4?QIIW1S;B{`|{WYwV6yV(s?t+ZcECuY1RMWS?>y zOqsYm9pKaPYuuREBl;>=yAtWP0LPABMM8YSamV zk6%5Wf7ymb5zi(KG)MI%`VX?DH}|C8bXK_FET^s$k#3VHCMb0&&_hJ9mXVh``nrzP zl^?~7W?0#plhoC#b1u}Dzqp^BwP}3#qIuCN--Yf$5v)`?u~rmY2G@Q88dJ3ziTahE_b8O;Ad{b6@*et#cHWpW!+QTJo;5Ryogjmy4%56-?gJFfBowt zfrCeAGLvJ`F5N=*)S+!wt9-5}VRYdSi2Yxbs%Qvdf95U8{*?>aaJ$@qflmRDhu>-b`X=HHL4Ue4SX(V6I8f2$EczauZS$$$O#10R+0EQvAn zx6{`(Od7Iirb=`ktGhVZ=+{OyPZPy%qc7l)ZuDReJ|l`nHQTquI*XGAHM@&!8ArOh z^rhDYHZU$=kC5%DXKM8iHN;pmREJBR;iU~Ik(`+m1IfoKPECaPww&4B-%^uxQ*bZY ztccXTWS}$Sp;}%s0~>IXmPd)#`&$j+_{P~fM!B=2yVY>c8)l=Wz@C^L4u8dqd9xXr%tSUJnX_SMwUjUu!}>zKWL55#F(l8+a1HM1h= zBt0P9W3PLn%flw?r^>7s8EZ#pvuZNN=v!Gqv185tJ`GM+MxS3BXto=mSl(rizuxz9 zO0pqj=9e}-CqJIg*FKNg-!({3GO<;N+F7uV=AWxvf5^Oq)2M@M&&Td8ZGREH;YJ|w#`(`^O|cJ$D#cTc zBaJfBQ%C+nhel-xja_?HX46*i!NIx5cu}jN~`lcPn+VwTYfHdPF&_AD52n#B1q;}{vES2mlXk4eUbCE&v7Ek~ZhmPb-7q3p0Q>ZZP=0t- zG4A5Zl!=3sLe4&>$iC>p z6UtZlCz8~ZdmINW6L{jwi-IlIA7+>m(8!czk}kF(#t5nH#7KkqsM{Qw;Mzy^z}cF83=Tv zKe7@L-P_+Zvw~_)mlG9Fq={981Qn5)KRHE2rR0Zr#xnx0k*T$6=^-5TH$B*AJ^TuN z>a&j0xJafV>sqsVnzIIf=Uc;k?@C-4<6?Fhr)ZE}o0ySrGjPDNL8;f>EhX8D)8Zaw z<-XanF>Ju_F?rjO?wgezk`5-1TLK*u?>^W&P*;vxLn*%d(lYw%va_URb!4mg57q9FlkUw@qm9!T#J4y?09#F(fE_^* zNJGo|1Ecw-NZ2YqrJHM}h>@|R{=Pa{FF7ZJAnf`0g4Z0$vNqBW#EUkQ8cQ$OnIw;f z*~C{zCK7EEQ)RZY$%ACiH+_>EMjFSBU&+|COWx@0{-UJgF!lhJ3+wbuWHmS16V&a7 zSu!l$V%fTmp4$1F#^r)?HooJzK7GYG5;tyt`lrf9qyF7L7Sauz_mQtUThmH9SsvJj z*v5NFV^#rMKEv({!8>*0^arU?pXrtRusD;+uCEXHP`sGYiGxXQ^7h@XTk!nPYf|Ew zMybD@*caI0Q9WSJib|oH6TQyha5J?v;?K+->Tojs4Z(u;I)PfZhwa#W>x^)v{CJXx zO>$%PCWUuAj*GYTpMUz0SLx4}{)y|ILASIM^v@h~^fc=|)3D=ZE5|&1rO3*y4;-`!YU7mMwv@|}JjB{4Dr#?%Q@YQNEnQ?pD>!sxq@R7>t$Wi0 zo2}R*UE39DHf}j1j*^WvUXLF))+Y|=94Jz>`q@e>J~3`fnC%0R=*v?_Wz}S><*PM? z6sgq|tY>M^+9gSkdx|S^gl^@##^*RG5ZNzZ#Q9AQSH#M&*LMt-jCSUnZRl{`(EE63 zoBmncRR(s>s$~!vyz-7sYc%D?2zFbryQqU5Gk-|@>w5+3l6p6Y*U0s0J%|~M^|WZH zkNaXYthcQoEMrr&{y;nS7a-M&E!YzBecYc`^^HZ=UGtFZ5t=-kcp&%C3u3Gdc9(Gc zpq?W3xY`*Z&!nCjpIf!LW4&(7_$HgX?%9{7aCe%1uK3ZH=hH^Jikl&&0r5nqz5P*% zWF5_TH`b%z%}zJFk3D_6{*9KpsE)7$xg7?p;tOUak5S+3^lH2Bue zmuLB=h)9o#y;9h7FmTR$(yJ{t@_orFkBNyM6Wx(REDn&Vb9>v+8!HlJPPBS?RyL~a zBJ!49%&P{8BtzPcMENeOFM^Nz zsJw6@OP>BVD5%^=to~v1zKYIVCkDB#LGnbwrZE}+R}^nA?DqVDHd7r1{X?tNn;yNS zVvpERE2v})yl&n$?z~ZsoZA?F>%=vFjl8b>ESZ5CzVqexLii8KkqJd=$D8&C8;j7= z2AW0*>h*@jwB24OuG;HsZqkvyxK?Pu+}-+t{F#{9r&gh(JI+J9rmkzdH2j@kzRI5m zip9VA&}$I#@EF-EvRKSPi{@-iQvH?cYiB7`|6FY0i!aNuXAUGrlS-RNI15tO)nS_aKRrb0p+@3T!>`!y*7bXqM z`aD(@vn0J;rme(oJijzrd-qkgTjYb75`#SAZp%Rntmhi4*5nwogWJnS2ipnE{*60X zjiQkZ&(b-M(ry-3~(p5Fe!2e375^Iw*`-ZY&OLujOU}fb!p9pa4Y>Mx&kHp!C z(snXK3?17|T5ESihh_UwJ7w6qcsIYqw|8mo;#4brxjbePbBc2%Ff2cu`c|ewM!!g- zjNqEql)M64D^t`VX6FNu8#H;r;PJt2z2vU%VwFAjxQ(no5lh#XKb23sxAp+5S2Q|| zRwCk_iKk#(o1_t+{AN>CfcGxV3A8AJml?Ml%|w8imSEr%h4QBt}*j zj8lG=jor~!Q65N-IwPOFtz~1~g|Ai{y(Fhc>Xi%+l5T3~ym+in-p}`DwvxDgo6=sY zg}>E?k~(*0&(-u@wv9*F@SYA+Tl~FBm`?HaFBOY4 zD*4M}bxU8T(7G>MyyN+7dVAS5edow~`juVSA6)(0tvhy`WDFY7#Hjuh*3(W7b^~~# z!j6dGSWmM6#eo*$@WT_F*k@%uZnU^nCG?o&FD9+T!~6>FPoH{)yEWN0X5*;!py%>6 zY~k%8LiQ$iT4KRPCdtPjn$rH(YL(cCSXXR8nB%RzEf*PzpIw${$#E9i9_36rG*vMX zCjP>|EvRNs)PVg4?1T4Q_9bkkoFi#<>LnRriV>CM1Li>o?@5@?77+N=TskCbhI5T_ zlP*Y?orp~>uU2-;D|QZaAgC5mu0D0nCI|#+&JbFHH~Cv#h+7^g(t#bdSZrI-Z&0RB zkgstgsb&VIrl$5sPcRt!u#1cJNhVLqp5!}k7ZaUnpfYnZTJi}LvJtk}C7Xc9;S`>{ zDYLv)uaqyK9-Av+DS`djTWx)@jE;u%d6^n)oW%ZrPE}R)@DjOaI%>J*0gmgdu}hiH z9cJ?mYcBjfZBc*Y`r$*L#bd>PexW|s%3eUGv(*G!gXF1ra-YrH%@1dowcVpl)SZ_i zrme9%K8e_i8r#N#q@lgBdf{o$6K6Hk1;u{0rpeP2Z#v96!uqV66h-W*+SP^rvb#DD4N|#5(J&4&K9^D@BDP2)a zimjn^;*4iW%UzD2zfH^!*z#@Bb)q+(Mv0Krd-&9R+D?l5J)>kWLvRM8lNFu#;wb9g zEXEP#hEpML19VGQH7APPy+ot64{B3;*w|yF81c`C#EQvcZo2CI-8YgOY&_509k#N^ zZ|dHqBZ!O4*;b2PkdltSP`pM`nZ#<`gRS{c!U*!-?!O~l#F|0&r&;Hl5AYFE1Nhmh z!%c~_8+1*-Yq8gRCykkxlkn9;jpZG?4@YCGt_rDRt2`w6&wUOW*-8^}-xz#0PeJsJ zwO)2UhjqNdT0f;E;110{C09=uS-~G#Mi~fp5yY>9gV_2Y~RmrbF4$gC9lz6 z^aKVq1WR}ll_M|fufkRv!Tv~0E2UVi8KDHnFR6co{ekOOPx5Jdia;*EtFvd!nPXj| z?g#veF7`Uw?o;#qLy`aYmmhxWMrZ5ZB=N>cFW(9NYAo~&yn>`<8V9--+JA@yD#o>9X*Dmvo8BedAYmf^WYUk_x!ZG z{wWQ#8&q1i^$&fq)r8Az(vX#_M@3cjs%&(=T~%_{3Q&t2phyiI$d?{lj3uTGm~$p^lY%B ztyLF2Vk4uI)Q0%P6%M57_E&pa*>3kf?Up5|YcWO*$l6vRB`9O&)UL|t@DU2kzLqII>8`zmdT}IS3h8@C%Lho zU`t$~z-J6&Rs%;~PG z>T?fES6aA8saW!2a>DOh*KI0B^|i7DyC&6&B-1x*r`lo3zV2NRKPJ9)C2Q!gQSyB~ z{MX8sd@I{MlWgy5)Y8wgp1FQ8X|W}{^1zy}tiAyk8~tA3U%vX#f8blFXu11wY?Tc) z0e1oY;n()XIvV|6*9Yu`%<(pKDJ)7*|c>CF( z-7U`l+SsiT?UQpzQpxUFry;e;AiKbR!$3R94O`VWCF#UE0(M{KUHVZMysr825YF_8 zYdfQ+a_e&R7%YnAjfxYJ!MIQsi}IS_&@QcZjMaxdG##&s4gcrt@kherdUHaY=rJwM zlWE75;^ZsBBPdL z^*2+SYJ@zgl-Qi^fk-|-hMDES1hyQ#s|B&$xVT`w{^n}Kh61&X^5Ve}u`&^p66(Rf zhO+lO>vky{brw*V9VjEzIQdOtcicpFTzee)%wt|^zky__X`=00@2J)Jre@nEu9 zOL6+ZV5gCh$vz>wrWir&PwENp3qig3&>z+<)c46y(3L9}y=(QhsZFcX`HRc;$=8vv z=X%|p)VFy{!4VFc2j6#!8_+3>4n`&EmCgTPwZ!h zv2@hR^BvFY)~ORTbd(&i$KLVrwME=d!dB(_X0X~kaLvia(F(gpYr07`qy1PQ)1Vsr zBXfHPD`32!f|)V+q#)$-WooWIp8_rB9glOvY60wlyp`ovGOG>J1?XZ<)>($*B7??O z^=y*zZoeFCi6Y%mFUHn}n^76MO@Sp8x3~V|#MJYw0R9U3*}5IZwZ?ZRMf?xko^+TM z>(WpTx=YFnh<6@PeO{jJ?9#A!JD7gXEI z>b$k(aWXz6o_OPN*Ki+p#bv+PS$|HH4bAGTb%;Row4Byi@RfFa3 zL>4PG+tuC8zU~k%&^>OS&)N~?SG^U_o$hE_Hzv~BocEro+hE`5PjpG_ET@M_4R(|t z*2uH1=i_&=-SIkqbnyJfo<~Cxd9!{ggbN!+7+ztoUSSuO@z>mJOJ^jC${#;&JWbil ze))KaATaZpPFs}hs{AsU_BQH_v{6)25x>{>e!&~gF#1n4KPIdvZ#mSRO`sgl-V(f2 zZzJc@B-?gkLt1fzkAds?=->LHb~9|qFBy61q=$}d;=o4p1B^sk;y%5w$kdmU?D$L7 zBggI7fg*0HO^F?5N`^A(4^nPppAO7_Q3Udp&S!ZKxKjfC-CbS=Stz8rPWQg!>DN;r zM>kjew$)xtQrXg7GL2G!t>Cy>@Wq&Gf4RlmYA1*AMR(vXl=j#Ee((>^U{zmcW{uow ziuQ(i$>`iqw?wHDXHdtX(``w=7L)DC{>3Mn82NX`_NE5|o z(+L-no8sLXf_hlTYTCbwk1TEIpkr?lD;l(+28tESs7qLM+|y|q7t(!LVYP>+GEOCF`9_(6cp_egxj2w1NelUI9ruIhBVE3u)8;99^&gS2Q zEc=}A+!^(62J5_T=Z@xBdAZuUr3CKCiAk*Ryfi6FzpUo&F5M87sDwM)Kt8=Z*Dxt$ zV?4ttx6M7}VFvk8qg`dUY^QOHoOlcMYRzQcl+dfssCa48=zxuz^>9-%&it)m*r;92 zXc>DibEHP1a)9Ec8|ZT+u*9HSw46=;#bL4~+sUM8qRu**G~qP3R_JL(_Y<$*>ZzW} zPl@a;ha}c*p3R-rif=?I}MX{PT9}vKI_sv$Pl`C_hfye*qhp@tk*k}r$g2?H;+7TeI|!< zx28`-eN!N0uPA?&axG7{LLi@Adt>%LI+Vvh^P8;J!6}nF$rnXK9*{=T_R`}kNAW*- ziYVs~&AQfUeeRWXk7;r!GFymz=fatLuhR)a*_?q+YQ}Vo`k9-QaUqw}I2)^JldyZ6 zb2JMK_SFqmV9S3@xe860h}~me85}!!hUy)=XGeE*k8;<3{Fuzv_R^lm>K@`L7UUNd z_(a)?31&N8T8KTpD{5@VjJtIrb^{0Y$J@=!mS(S}&E6eDU%R^RPh+}m53`}A@@ly& za2^*)FdMGF6>y~Cnuh69pvuPMmO$uQoPO4D2yo{ zSizah7HqD}DJM!AmYXnd2A30@I`QWY;S^=mWXgxj)gPMGy<{w}y3MG*6+Xgm%{Cs* zQX@=Up)3!v?!VzLq2`V~BfzE6pDJojG>d1By2CLE`Tc)wG(^_?U*7OxFU|HV)L8>hqnB8&y{A)mDHuY-W_f9HrAmi`aF|L>fE{=*>N zzy0G-^S&v#Uwj;DxLf4?&mV`H_f5g|;^R=m)gZtB_;INDy)C$2eH?1I8szsMKMpm& zx5eUDABUR7?eT}7J`Od1XpF@#KMpmE+v5*EeH?23&=`weejI8RHOC)+_&C)3u{9RG z{y5YuYK}kt@Nua5V{82J_5Tk;&G?h9tzmDc!tICzqo{G4Y$NwbJsQLI=eU(uZ0Lr1 zvz-c>S86uj>wMEcu)&(zxsJb7P))a7wY_Lld(lO{jAt$bhX$Q;2anOPPx3e0L`hT4 zBmDdxqy)>i2g|=WJjS83-2@J-Ap5f3?rZ<;eEo6kd0^AJZ{nu1J=2TC?jQI}?pb&6 zm0MZZEpg*1J^ROO-Wpxd`uX%ya!Tb^F-xm=Jd&^QPrU}&z06hdOU75V;NuiDpH$A8 zv?X5$WiKBVxRIvx}aF=hasjut$)vHzE<2BP4^x}apegg|F|nHZW)BI?)20;(~z%kcBM7`ljvUO(jPv? zJiiS;w)4k*<6|HEIBt9#pFhqiALs6m#~UAyk^bGW=+)C{LquA!qnAU{w#xqKoW9aB zTC`qIdQ`e$)Tx`Y>>^e5o^`2}2RV7cZc(oio7g66)&(iyE-rB#$;_7E!0c9t$C&h}<@cHdcgTG}Zr{6@mu%)(Yeb+?U!70)L?;^#_od$>?M zR>1rhKlr{x*TcaQ&x`#8-mPbS(vp18&e|cGOMDK*GCfNtXKOO)hbP!ChPmD(4c@d8 zl%{EBZ~2b{{zMO$PSiHF4mULa#9w>3vYs9$$+(DcC(zTg*3NY2xI>R zRhm5L6`z7quvNF`zrLK?n05p`T_-b=^IW-P()*>>-@kDD3%mwkyoH$^wk^T?hn3&I zNCpr#_i|9{dayg0d~4@$%FF~$c6G8?*rhbdB+DkCmcVy|?_V6mc3TImqWL|I>mTr* zKY#NrTRa|o1>W+0JP=j_;(!bHC=Qg3enS*p5R}mkuOD}n{c=N0S(Sa_#3i}ak z)cH=U!S!|+$?u(ZJ0=b)NX~a!$%RfU2|BGL=w`beNLClP3Yr`G=f3{jKj#LmKfodY z|5aC<{tJxkihDpe_-9?w3aAg$6;&%2cg4Aup8I;=6TTqs)ztJ5_K^T~XV9OnYx&hHc0k{a))(Yc53{mp4~VMdkEcCvQEju9l>_RJl@ z9jREn3%~*F&t3IDSWR40>3=QpA}H}+_2I(5z{oy4%9Y1*&{F^R=~Nu_x)0Nbb!!&) z;XfT-C&>Km!)whyA6~OTuyF5z9r3RZuP3<#>=ujqw^r5SJ>Xv+UM~*Vtom>C{Rg0C z7}o!+?=J;a!qWat-~XPgj1SoN(?QX2>;2=yYtX>+ z^Ozv~=P}`r2kY~tg@e-m)p$_#7Z^DnECqcLHuv8f4-|p=FynzJd+~T63fghawvxORA zACLlX!{)9P*h_UXb~b_)>EtqhMtM}%8aqXr%XR+6+6*2dPg|!;{_J`8` zMriZ$AN>==wME5%{(Xz8n|W9|sb`ig-!{KR*JE$^&dk|z{?`kebpGZ1CjCcM&%gP{ zZ|6QA`19AwgJcrP(qiru)p>?1Mgj2lPY&Yex}cVpdR0rr*171^VL?*0i+9vNlh&79 z!~#ip%@gPM>i4yTAxYJKF7g;jgpan)xAgm3rjexT{BL}V(wD45rJvCE^s7b097#gU zG#00S4N0WV2P`I!kVLrt`^97mNrqc~SWI?5N2L#U=l3I(8Ila!k1sSb+}W-oiSX*k zh20X8QY6WA0JT6ni6o|ufWTc^q8^n#&lK4CAu&Y~sSO|(NUk7>X#!U*B}l?^qG(~~ zhqX)~3GYcRva11=enK`EF+q~ddtBrfB$0CCBE?AJ5XD8tk;FHNi--ahKpQAl28FI@(@W{4{?z(B;h&EMRqo!(l`BS%jSil5A*^fBymWAlLa85NaA~o zixeSAD3gnfB8k)mkPD1(2M3jY!b4CCB!+0>4Qhcl1WAO?0Rr3M0g~`80V50~Z;{0J zJg5cQZ<hU7kysGI?{K>G$sczgkYBdu5qD*a&{ zFkC=lfFzl)7HETzq!sR#a6B(WlEP9D7$hS|G7MV`TG3Wi`n*4b7K5|KaU>D82Z2En zh$NvGK`qc0Aj$A0K;Yc;8c9@qKwyx3{Q{MKAxt0IV@Q&qKBF@~TEZDH07*hqL12*F zLlPAgPz$uL(Bvo}aGw278!CO%%OEgF^pPa=2&e^Ge-+{p3F@qkG3~Pbu6%s!r;W-Fufi@3Gs$h%3Baut5~&|R zVDQABfh5AVAbm*sk)$xpPH=uqhD4|vm42uf7x@uM63%gv^GIT9%0=!XiPU`KqL1cN!1-LBG`*epOGiLsK00-39l9W90qjH zpOHk$1$1QCqqC93*ApD|LHiO(q~Q4r90a!XA=9@8y%G{lB=Jr3^_*`p2Vj3fl2$nM z!J+0hl4J&fz#!>HlGYKeh2a9)&HbqKrDg$v1DXbs_&R~WAn`>K;a8v*XtR(+)bidUiSG;`aB%iP5(jw@7$molq!n%#XgiT)_o1-n0X+HN*N&Vz$_I>9DLy+95|M1L6Xp4xX71pQ0Zr0=OSuI z(t77xa54e{b4QZG>s*A6B%yv>q!~>DS1p{1!bV>A7L|U&H7=rxB-*FA2n9)e&vFqO zlBfu*nZG4N9p=J865st?gm)B`zVI?Gau7)pp25=uu=U-LB$LBMQjo+nlZ!MVN!3%( z#bC!-JBCW%*AMLckbH+EQqMpw(7GatX(b@Ad)`2jglG^LB#lTi3`ZDf*NmgmmwF5W zgM+}gNRk;1dL<+-NWxPAYXLSHNqipx0*9IgB&kXRfkCo*0+l{*6{rPT6(sSk0R#?c z&S>Hb0)vE#B*V3!7GGfNkwh5o{BYP@HHk_;;TI4XB+5uq2wMzVCnRZw`&cL#oPR?S z2Tw3sLh>9*T4Aq*cI6Z*eN$oMg-@>F6mS4ZGUGsCkT@bq>(`(bXs;uQ@OD7pL{o<( zrj8&mNLEav(&zmO)B^2(B&pgB2%OFwkVJ|X%rB56A&G+ss0G?uB&mWe1}DwsGpO{n zML=MXC?QFxDBLcGSRa1Rek=B8h_x z2n-Sul1RawAKG7$B;gwn7(C2)h9se7;7|dQW$#ewJ4k?9pjAK;VYu_dgBg1yDeMA) zL6U$ZnL9yX(AFSH;rosZ4w;tnEL*r_GvUbyv@%Fi2k4 z!%=8iNWvom2pnp7mZH+H+6r0>5*(6bZUnVJOGJ`Zm1%%lYJt`Q zNrXiKfzw$Sl4Q2QO$lbe$7llkKD5(FQYZidgOjG@3RL<$yr9J(F-MZBEua=?uOW%) zc0l0N{0K=@WI$k$Od*NsCQu8syH}#p=Y=f>4*<-NB-9K92FX<L5nPq^$gPAL6;tT?Vqy$N{*`St8U?-4-mjwts zWZJbFl|JutK;VsICP>m6403_w7bF=D=OV>O!b{^K<47W$#YM!|pwd?<<|4*OlKBSQ zE(g=Uj3iY{Yrr*E;O@MKNFpr5MaGb1coP@dxfYrJAueKsB-&bBBos+}>$zI22uU2w zxM~?ilB!CuC&T@72QMmpUIDQ6Au&XfLfBuR4M7sqIuID_1rLyf=T{IIByZ8A7Ssam zH|tR8o0bCt``QU4X`KO+I3&SHQV0hnXzwG5Fx-@|N54T5sXWkPkcfSWN?-d4NFQ1Q zBnf>A2pl(pkVI-f*pneCL=uMz7#Of4NWufBBse;W@}bh_6$gPqavVu~tKbd;Y#@?k z!rc;%fdxp?x)%0*Fl@d?5~*qs7_?unN2T8i2^?vUAxUT(7{?$9K$2m2bOG%>BuRJ% za)IOdD>ZBneFiwLm+JB)+Ne z4?|#X+P(pmK9304$KZ6Pha@T)pcZKTkfiW7AaDlELlVxoYJpZ{BP#vO zTY$iMmVhL!aO=az1zbXs%<~`@NSH{%o5w{4k;L>{&4ue>U@c#5LZx4r0@8;O>LQ7+ z4G0X9i%6n<6K;KAbCF~i?qjeW29TsM7z751umCFkP-iZJN0QbAE^+}$c(S+%14$B| zfyYX~T>6njcq`cYkO*x?rQbTvRm+b^QrN{s&Lc_H5Er?NBvPVWx%4541B0uUZCg<3 z`##_zI!M9`$1%9GokNnUaIp0u$w3kxI4D8eizHQdKo^6%^j1Ms`U$5&V324di7=ci zp*@Qv4%r|u*m3S4$#4n?43ZusNyr4XKr6TvmA+{fAh3ICA&GD?2n>>+(FAT6XtR;T z^acnF4gxRHBopk(kZjq8N}m^w{HDNaB8kK2w-@dhfq0;A_0O><}6iEsp zffJ24l4#3=z#zGaBs`yj7K63}Nwhx!1WsogMNsKGOoH^`5#tdgG5rkG0v~vF21y)_ zfb=2BL=v7DF4B%9p@v*!!**2qt$tkO2P7$^aFNqUGW-h{$v_hAaPVX>*kWx+lBvW+ z`2T}SUnQ7}e2*lBYFxw%NvbrtNIH@TpXMSjkVFbj7I0@<|1~OoUp&|@kQ_#m&{@!5 zp!GzOVK^nhT{;a(5-dSrkhG!+59pQ9@`<9-=UoCjGUzymkR+1?_hdjk&;*WS(550u zAq)(5&lV)%{S5>L$(LfN^b_nS7mhBVRYMZtQ-Huhz#U1dd_iE4(2+!&1Zsh{8A%e% z0f9r!x^GbF51WE628k+?wA#R0fTbWw6>Kp$pwWV36EE5>rbM7_^N@f-Ua2(2?Opvql`1eyctR43ck=gog}jfz|~{cwu00I!i_p z2P;qud^BhSl7#LAxj?dd7b<<>)qx8i4??1XB)+$}YH>yqo+&OuMH15lE>e#q4k=t@ zl>{pNgoj*28A+;IxQG*y6efZvwZcaJ4M`l9f+w{?@*GKcO1WxTxf_+f=?oV+fF!bbRf8YUPlu+D8c=+4oO0pV7oxFLK2m}uQljm(C$Z)P+LG?FK|E-sSTiu zL6U?dDt55%16zwE!;rwfwpGLrZv0|I+=B9aWh0)auo zMiQwTpcZJqkVd61Oa%mv8;VF$SPKG!goGq2G*An)zamL1%mt24&ya+73E0OVS+)n2 zzIG}I3|a*w;YkDpj)C?_()tp#7$gZuVwwbMfwl%oGU09sN7|)0WcqKw&_Cl3`5{7$mbuk`M`M zfmT`(m3}MC1&$jhk;FFv1O`bsl7xnXz@UACB*Iq#fuqw5l4R}#fk7grgi5~()&i{s zl4wVPz~C4dh9sdWATUTCBZ*1`s0G?-BpHSTjflC*lEp7O$y z(sQegs#+6?uFjSR$#!I?EUq%dxYjAsR)@>w{a(i0(o>pbCu~J(Y%Sz5t^)oX1YEvW zou{XH{_X-icMyuo3%UPwB5>VJ^@Oj%;(gp@kU$xKeV@Ir*aCguD!(EL&jCPS4$qLp zbO#_X{T$pU(IzskHqGG(T3#dbqfFy9Qc~5GP1a9N^Wa+o4 z^nDGu0^5fqa4&dY3tU6%Pv678Td1D<73O zoHr_iMtb z!!_M3|H7<(zy8{PcXJ7Tps&e1+KQ+5n@n%J%wFPqx6A9l6|27Wzy0?A&F{ty0&SY# Q)_eE&tZH`+*|GBf08ypgy#N3J delta 151033 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 diff --git a/scripts/system/html/css/miniTablet.css b/scripts/system/html/css/miniTablet.css index 75da2eaf82..35a8354750 100644 --- a/scripts/system/html/css/miniTablet.css +++ b/scripts/system/html/css/miniTablet.css @@ -8,6 +8,14 @@ Distributed under the Apache License, Version 2.0. See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html */ +@font-face { + font-family: HiFi-Glyphs; + src: url(../../../../resources/fonts/hifi-glyphs.ttf), + url(../../../../fonts/hifi-glyphs.ttf), + url(../../../../interface/resources/fonts/hifi-glyphs.ttf), + url(../fonts/hifi-glyphs.ttf); +} + * { box-sizing: border-box; padding: 0; @@ -26,12 +34,12 @@ body { section { background-color: #404040; position: relative; - padding: 15px; + padding: 13px; } section .button { width: 100%; - height: 88px; + height: 90px; background-color: #252525; margin-top: 10px; text-align: center; @@ -61,43 +69,33 @@ section .button { } img { - width: 60px; + width: 44px; } #mute { - padding-top: 12px; + padding-top: 19px; } #bubble { - padding-top: 14px; + padding-top: 19px; } -footer { +#expand { position: absolute; - bottom: 0; - width: 100%; - height: 30px; - text-align: center; - color: #e3e3e3; - font-weight: bold; - font-size: 26px; + right: 13px; + bottom: 13px; + width: 40px; + height: 40px; + border-radius: 20px; } - footer div { + #expand span { display: inline-block; - height: 14px; - width: 30px; + transform: rotate(45deg); position: relative; - top: 14px; - } - - footer div p { - position: relative; - top: -15px; - } - - footer .button:hover { - border: 1px solid #e3e3e3; - border-radius: 3px; - margin: -8px; + left: 0; + top: 5px; + font-family: hifi-glyphs; + font-size: 24px; + color: #ffffff; } diff --git a/scripts/system/html/miniTablet.html b/scripts/system/html/miniTablet.html index 3ba8d66196..1f18228987 100644 --- a/scripts/system/html/miniTablet.html +++ b/scripts/system/html/miniTablet.html @@ -23,10 +23,8 @@ See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.

+
`
-
-

...

-
diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 25a3389539..403511365b 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -19,7 +19,7 @@ var // Base overlay proxyOverlay = null, PROXY_MODEL = Script.resolvePath("./assets/models/tinyTablet.fbx"), - PROXY_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0046 }, // Proportional to tablet proper. + PROXY_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0045 }, // Proportional to tablet proper. PROXY_POSITION_LEFT_HAND = { x: 0, y: 0.1, // Distance from joint. @@ -42,12 +42,12 @@ // UI overlay. proxyUIOverlay = null, PROXY_UI_HTML = Script.resolvePath("./html/miniTablet.html"), - PROXY_UI_DIMENSIONS = { x: 0.0577, y: 0.0905 }, + PROXY_UI_DIMENSIONS = { x: 0.059, y: 0.0865 }, PROXY_UI_WIDTH_PIXELS = 150, METERS_TO_INCHES = 39.3701, PROXY_UI_DPI = PROXY_UI_WIDTH_PIXELS / (PROXY_UI_DIMENSIONS.x * METERS_TO_INCHES), PROXY_UI_OFFSET = 0.001, // Above model surface. - PROXY_UI_LOCAL_POSITION = { x: 0, y: 0, z: -(PROXY_DIMENSIONS.z / 2 + PROXY_UI_OFFSET) }, + PROXY_UI_LOCAL_POSITION = { x: 0.0002, y: 0.0024, z: -(PROXY_DIMENSIONS.z / 2 + PROXY_UI_OFFSET) }, PROXY_UI_LOCAL_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), proxyUIOverlayEnabled = false, PROXY_UI_OVERLAY_ENABLED_DELAY = 500, From 15fcafa2f5babf9ec6ac53302c4cdc8e4c2af5ae Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Aug 2018 22:13:09 +1200 Subject: [PATCH 128/744] Use SVGs for buttons --- .../system/html/css/img/mt-bubble-a-hover.svg | 5 ++ .../html/css/img/mt-bubble-a-normal.svg | 5 ++ .../system/html/css/img/mt-bubble-i-hover.svg | 5 ++ .../html/css/img/mt-bubble-i-normal.svg | 5 ++ .../system/html/css/img/mt-expand-hover.svg | 31 +++++++ .../system/html/css/img/mt-expand-normal.svg | 31 +++++++ scripts/system/html/css/img/mt-mute-hover.svg | 5 ++ .../system/html/css/img/mt-mute-normal.svg | 5 ++ scripts/system/html/css/miniTablet.css | 82 +++++++++---------- scripts/system/html/miniTablet.html | 2 +- 10 files changed, 134 insertions(+), 42 deletions(-) create mode 100644 scripts/system/html/css/img/mt-bubble-a-hover.svg create mode 100644 scripts/system/html/css/img/mt-bubble-a-normal.svg create mode 100644 scripts/system/html/css/img/mt-bubble-i-hover.svg create mode 100644 scripts/system/html/css/img/mt-bubble-i-normal.svg create mode 100644 scripts/system/html/css/img/mt-expand-hover.svg create mode 100644 scripts/system/html/css/img/mt-expand-normal.svg create mode 100644 scripts/system/html/css/img/mt-mute-hover.svg create mode 100644 scripts/system/html/css/img/mt-mute-normal.svg diff --git a/scripts/system/html/css/img/mt-bubble-a-hover.svg b/scripts/system/html/css/img/mt-bubble-a-hover.svg new file mode 100644 index 0000000000..36abbf5c34 --- /dev/null +++ b/scripts/system/html/css/img/mt-bubble-a-hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/system/html/css/img/mt-bubble-a-normal.svg b/scripts/system/html/css/img/mt-bubble-a-normal.svg new file mode 100644 index 0000000000..e6e8021bf5 --- /dev/null +++ b/scripts/system/html/css/img/mt-bubble-a-normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/system/html/css/img/mt-bubble-i-hover.svg b/scripts/system/html/css/img/mt-bubble-i-hover.svg new file mode 100644 index 0000000000..e39dff3888 --- /dev/null +++ b/scripts/system/html/css/img/mt-bubble-i-hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/system/html/css/img/mt-bubble-i-normal.svg b/scripts/system/html/css/img/mt-bubble-i-normal.svg new file mode 100644 index 0000000000..ef2591ba16 --- /dev/null +++ b/scripts/system/html/css/img/mt-bubble-i-normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/system/html/css/img/mt-expand-hover.svg b/scripts/system/html/css/img/mt-expand-hover.svg new file mode 100644 index 0000000000..eea3487bd8 --- /dev/null +++ b/scripts/system/html/css/img/mt-expand-hover.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/html/css/img/mt-expand-normal.svg b/scripts/system/html/css/img/mt-expand-normal.svg new file mode 100644 index 0000000000..7b5b59a7c9 --- /dev/null +++ b/scripts/system/html/css/img/mt-expand-normal.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/html/css/img/mt-mute-hover.svg b/scripts/system/html/css/img/mt-mute-hover.svg new file mode 100644 index 0000000000..59c38ca5a0 --- /dev/null +++ b/scripts/system/html/css/img/mt-mute-hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/system/html/css/img/mt-mute-normal.svg b/scripts/system/html/css/img/mt-mute-normal.svg new file mode 100644 index 0000000000..1f25bce9c6 --- /dev/null +++ b/scripts/system/html/css/img/mt-mute-normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/system/html/css/miniTablet.css b/scripts/system/html/css/miniTablet.css index 35a8354750..e633f93664 100644 --- a/scripts/system/html/css/miniTablet.css +++ b/scripts/system/html/css/miniTablet.css @@ -8,14 +8,6 @@ Distributed under the Apache License, Version 2.0. See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html */ -@font-face { - font-family: HiFi-Glyphs; - src: url(../../../../resources/fonts/hifi-glyphs.ttf), - url(../../../../fonts/hifi-glyphs.ttf), - url(../../../../interface/resources/fonts/hifi-glyphs.ttf), - url(../fonts/hifi-glyphs.ttf); -} - * { box-sizing: border-box; padding: 0; @@ -37,34 +29,14 @@ section { padding: 13px; } -section .button { +.button { width: 100%; height: 90px; - background-color: #252525; margin-top: 10px; text-align: center; - border-radius: 8px; } - section .button.off { - border: 2px solid #6a6a6a; - background-color: #303030; - } - - section .button.off:hover { - border: 2px solid #1fc6a6; - } - - section .button.on { - border: 2px solid #1fc6a6; - background-color: #1fc6a6; - } - - section .button.on:hover { - border: 2px solid #e3e3e3; - } - - section .button:first-child { + .button:first-child { margin-top: 0; } @@ -74,28 +46,56 @@ img { #mute { padding-top: 19px; + background-size: 100% 100%; } + #mute.off { + background-image: url("./img/mt-mute-normal.svg"); + } + + #mute.off:hover { + background-image: url("./img/mt-mute-hover.svg"); + } + #bubble { padding-top: 19px; + background-size: 100% 100%; } + #bubble.off { + background-image: url("./img/mt-bubble-i-normal.svg"); + } + + #bubble.off:hover { + background-image: url("./img/mt-bubble-i-hover.svg"); + } + + #bubble.on { + background-image: url("./img/mt-bubble-a-normal.svg"); + } + + #bubble.on:hover { + background-image: url("./img/mt-bubble-a-hover.svg"); + } + + #bubble img { + position: relative; + left: -2px; + } + #expand { position: absolute; - right: 13px; + right: 14px; bottom: 13px; width: 40px; height: 40px; - border-radius: 20px; + background-size: 100% 100%; } - #expand span { - display: inline-block; - transform: rotate(45deg); - position: relative; - left: 0; - top: 5px; - font-family: hifi-glyphs; - font-size: 24px; - color: #ffffff; + #expand.off { + background-image: url("./img/mt-expand-normal.svg"); } + + #expand.off:hover { + background-image: url("./img/mt-expand-hover.svg"); + } diff --git a/scripts/system/html/miniTablet.html b/scripts/system/html/miniTablet.html index 1f18228987..4b3129fca9 100644 --- a/scripts/system/html/miniTablet.html +++ b/scripts/system/html/miniTablet.html @@ -23,7 +23,7 @@ See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.
-
`
+
From bf348f43aa55d790423f6e5a828e0372e9843f90 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 29 Aug 2018 09:55:54 -0700 Subject: [PATCH 129/744] Prepare for runtime CPU dispatch of all SIMD functions --- libraries/audio/src/AudioHRTF.cpp | 54 ++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index 23adcde0c5..aef4ef3326 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -276,23 +276,8 @@ static void FIR_1x4_SSE(float* src, float* dst0, float* dst1, float* dst2, float } } -// -// Runtime CPU dispatch -// - -#include "CPUDetect.h" - -void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); -void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); - -static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) { - - static auto f = cpuSupportsAVX512() ? FIR_1x4_AVX512 : (cpuSupportsAVX2() ? FIR_1x4_AVX2 : FIR_1x4_SSE); - (*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch -} - // 4 channel planar to interleaved -static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { +static void interleave_4x4_SSE(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { assert(numFrames % 4 == 0); @@ -323,7 +308,7 @@ static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, f // process 2 cascaded biquads on 4 channels (interleaved) // biquads computed in parallel, by adding one sample of delay -static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { +static void biquad2_4x4_SSE(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { // enable flush-to-zero mode to prevent denormals unsigned int ftz = _MM_GET_FLUSH_ZERO_MODE(); @@ -388,7 +373,7 @@ static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3] } // crossfade 4 inputs into 2 outputs with accumulation (interleaved) -static void crossfade_4x2(float* src, float* dst, const float* win, int numFrames) { +static void crossfade_4x2_SSE(float* src, float* dst, const float* win, int numFrames) { assert(numFrames % 4 == 0); @@ -435,7 +420,7 @@ static void crossfade_4x2(float* src, float* dst, const float* win, int numFrame } // linear interpolation with gain -static void interpolate(float* dst, const float* src0, const float* src1, float frac, float gain) { +static void interpolate_SSE(float* dst, const float* src0, const float* src1, float frac, float gain) { __m128 f0 = _mm_set1_ps(gain * (1.0f - frac)); __m128 f1 = _mm_set1_ps(gain * frac); @@ -453,6 +438,37 @@ static void interpolate(float* dst, const float* src0, const float* src1, float } } +// +// Runtime CPU dispatch +// + +#include "CPUDetect.h" + +void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); +void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); + +static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) { + + static auto f = cpuSupportsAVX512() ? FIR_1x4_AVX512 : (cpuSupportsAVX2() ? FIR_1x4_AVX2 : FIR_1x4_SSE); + (*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch +} + +static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { + interleave_4x4_SSE(src0, src1, src2, src3, dst, numFrames); +} + +static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { + biquad2_4x4_SSE(src, dst, coef, state, numFrames); +} + +static void crossfade_4x2(float* src, float* dst, const float* win, int numFrames) { + crossfade_4x2_SSE(src, dst, win, numFrames); +} + +static void interpolate(float* dst, const float* src0, const float* src1, float frac, float gain) { + interpolate_SSE(dst, src0, src1, frac, gain); +} + #else // portable reference code // 1 channel input, 4 channel output From 6f151ad2837cb252dd8ea16c5baa3e415ca23363 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 29 Aug 2018 10:03:40 -0700 Subject: [PATCH 130/744] Consistent parameter order for SIMD functions --- libraries/audio/src/AudioHRTF.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index aef4ef3326..20d782a414 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -420,7 +420,7 @@ static void crossfade_4x2_SSE(float* src, float* dst, const float* win, int numF } // linear interpolation with gain -static void interpolate_SSE(float* dst, const float* src0, const float* src1, float frac, float gain) { +static void interpolate_SSE(const float* src0, const float* src1, float* dst, float frac, float gain) { __m128 f0 = _mm_set1_ps(gain * (1.0f - frac)); __m128 f1 = _mm_set1_ps(gain * frac); @@ -465,8 +465,8 @@ static void crossfade_4x2(float* src, float* dst, const float* win, int numFrame crossfade_4x2_SSE(src, dst, win, numFrames); } -static void interpolate(float* dst, const float* src0, const float* src1, float frac, float gain) { - interpolate_SSE(dst, src0, src1, frac, gain); +static void interpolate(const float* src0, const float* src1, float* dst, float frac, float gain) { + interpolate_SSE(src0, src1, dst, frac, gain); } #else // portable reference code @@ -731,7 +731,7 @@ static void crossfade_4x2(float* src, float* dst, const float* win, int numFrame } // linear interpolation with gain -static void interpolate(float* dst, const float* src0, const float* src1, float frac, float gain) { +static void interpolate(const float* src0, const float* src1, float* dst, float frac, float gain) { float f0 = gain * (1.0f - frac); float f1 = gain * frac; @@ -983,8 +983,8 @@ static void setFilters(float firCoef[4][HRTF_TAPS], float bqCoef[5][8], int dela azimuthToIndex(azimuth, az0, az1, frac); // interpolate FIR - interpolate(firCoef[channel+0], ir_table_table[index][azL0][0], ir_table_table[index][azL1][0], fracL, gain * gainL); - interpolate(firCoef[channel+1], ir_table_table[index][azR0][1], ir_table_table[index][azR1][1], fracR, gain * gainR); + interpolate(ir_table_table[index][azL0][0], ir_table_table[index][azL1][0], firCoef[channel+0], fracL, gain * gainL); + interpolate(ir_table_table[index][azR0][1], ir_table_table[index][azR1][1], firCoef[channel+1], fracR, gain * gainR); // interpolate ITD float itd = (1.0f - frac) * itd_table_table[index][az0] + frac * itd_table_table[index][az1]; From 3e17556cc1c14c7263d8f27cf440476c7be22451 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 29 Aug 2018 10:47:02 -0700 Subject: [PATCH 131/744] Prefer static_assert() over assert() --- libraries/audio/src/AudioHRTF.cpp | 6 +++--- libraries/audio/src/avx2/AudioHRTF_avx2.cpp | 2 +- libraries/audio/src/avx512/AudioHRTF_avx512.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index 20d782a414..7895a70595 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -240,7 +240,7 @@ static void FIR_1x4_SSE(float* src, float* dst0, float* dst1, float* dst2, float float* ps = &src[i - HRTF_TAPS + 1]; // process forwards - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { @@ -425,7 +425,7 @@ static void interpolate_SSE(const float* src0, const float* src1, float* dst, fl __m128 f0 = _mm_set1_ps(gain * (1.0f - frac)); __m128 f1 = _mm_set1_ps(gain * frac); - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { @@ -505,7 +505,7 @@ static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* ds float* ps = &src[i - HRTF_TAPS + 1]; // process forwards - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { diff --git a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp index e89128b173..501f010236 100644 --- a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp +++ b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp @@ -44,7 +44,7 @@ void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3 float* ps = &src[i - HRTF_TAPS + 1]; // process forwards - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { diff --git a/libraries/audio/src/avx512/AudioHRTF_avx512.cpp b/libraries/audio/src/avx512/AudioHRTF_avx512.cpp index a8bb62be35..fcec81fa4c 100644 --- a/libraries/audio/src/avx512/AudioHRTF_avx512.cpp +++ b/libraries/audio/src/avx512/AudioHRTF_avx512.cpp @@ -44,7 +44,7 @@ void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* ds float* ps = &src[i - HRTF_TAPS + 1]; // process forwards - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { From 9711076daea27342310275d1cffd3f6e193030a5 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 29 Aug 2018 10:49:29 -0700 Subject: [PATCH 132/744] Use a queued signal for pending datagrams --- libraries/networking/src/udt/Socket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 56b7521d7a..d764b9e8b2 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -37,7 +37,7 @@ Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) : _shouldChangeSocketOptions(shouldChangeSocketOptions) { connect(&_udpSocket, &QUdpSocket::readyRead, this, &Socket::readPendingDatagrams); - connect(this, &Socket::pendingDatagrams, this, &Socket::processPendingDatagrams); + connect(this, &Socket::pendingDatagrams, this, &Socket::processPendingDatagrams, Qt::QueuedConnection); // make sure we hear about errors and state changes from the underlying socket connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), From f9d14e135149d977640f09308ec791cf6fb52b28 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 29 Aug 2018 11:17:11 -0700 Subject: [PATCH 133/744] filter time-cost drivers for region regulation --- interface/src/Application.cpp | 14 +++-- libraries/shared/src/NumericalConstants.h | 1 + libraries/workload/src/workload/Space.h | 3 +- libraries/workload/src/workload/ViewTask.cpp | 56 +++++++++++++++----- libraries/workload/src/workload/ViewTask.h | 29 ++++++---- 5 files changed, 73 insertions(+), 30 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 44bba6250b..0c25fd59a9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5795,15 +5795,13 @@ void Application::update(float deltaTime) { auto t5 = std::chrono::high_resolution_clock::now(); workload::Timings timings(6); - timings[0] = (t4 - t0); - timings[1] = (t5 - t4); - timings[2] = (t4 - t3); - timings[3] = (t3 - t2); - timings[4] = (t2 - t1); - timings[5] = (t1 - t0); - + timings[0] = t1 - t0; // prePhysics entities + timings[1] = t2 - t1; // prePhysics avatars + timings[2] = t3 - t2; // stepPhysics + timings[3] = t4 - t3; // postPhysics + timings[4] = t5 - t4; // non-physical kinematics + timings[5] = workload::Timing_ns((int32_t)(NSECS_PER_SECOND * deltaTime)); // game loop duration _gameWorkload.updateSimulationTimings(timings); - } } } else { diff --git a/libraries/shared/src/NumericalConstants.h b/libraries/shared/src/NumericalConstants.h index 4c24a73226..8377c48960 100644 --- a/libraries/shared/src/NumericalConstants.h +++ b/libraries/shared/src/NumericalConstants.h @@ -36,6 +36,7 @@ const float METERS_PER_MILLIMETER = 0.001f; const float MILLIMETERS_PER_METER = 1000.0f; const quint64 NSECS_PER_USEC = 1000; const quint64 NSECS_PER_MSEC = 1000000; +const quint64 NSECS_PER_SECOND = 1e9; const quint64 USECS_PER_MSEC = 1000; const quint64 MSECS_PER_SECOND = 1000; const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND; diff --git a/libraries/workload/src/workload/Space.h b/libraries/workload/src/workload/Space.h index 310955f4c6..7dcb2217f7 100644 --- a/libraries/workload/src/workload/Space.h +++ b/libraries/workload/src/workload/Space.h @@ -70,7 +70,8 @@ private: using SpacePointer = std::shared_ptr; using Changes = std::vector; using IndexVectors = std::vector; -using Timings = std::vector; +using Timing_ns = std::chrono::nanoseconds; +using Timings = std::vector; } // namespace workload diff --git a/libraries/workload/src/workload/ViewTask.cpp b/libraries/workload/src/workload/ViewTask.cpp index 5d9953cdf4..b05a4c0c5f 100644 --- a/libraries/workload/src/workload/ViewTask.cpp +++ b/libraries/workload/src/workload/ViewTask.cpp @@ -102,9 +102,16 @@ void ControlViews::run(const workload::WorkloadContextPointer& runContext, const // Export the ranges and timings for debuging if (inTimings.size()) { - _dataExport.timings[workload::Region::R1] = std::chrono::duration(inTimings[0]).count(); + // NOTE for reference: + // inTimings[0] = prePhysics entities + // inTimings[1] = prePhysics avatars + // inTimings[2] = stepPhysics + // inTimings[3] = postPhysics + // inTimings[4] = non-physical kinematics + // inTimings[5] = game loop + _dataExport.timings[workload::Region::R1] = std::chrono::duration(inTimings[2] + inTimings[3]).count(); _dataExport.timings[workload::Region::R2] = _dataExport.timings[workload::Region::R1]; - _dataExport.timings[workload::Region::R3] = std::chrono::duration(inTimings[1]).count(); + _dataExport.timings[workload::Region::R3] = std::chrono::duration(inTimings[4]).count(); doExport = true; } @@ -115,11 +122,29 @@ void ControlViews::run(const workload::WorkloadContextPointer& runContext, const } } -glm::vec2 Regulator::run(const Timing_ns& regulationDuration, const Timing_ns& measured, const glm::vec2& current) { - // Regulate next value based on current moving toward the goal budget - float error_ms = std::chrono::duration(_budget - measured).count(); - float coef = glm::clamp(error_ms / std::chrono::duration(regulationDuration).count(), -1.0f, 1.0f); - return current * (1.0f + coef * (error_ms < 0.0f ? _relativeStepDown : _relativeStepUp)); +glm::vec2 Regulator::run(const Timing_ns& deltaTime, const Timing_ns& measuredTime, const glm::vec2& currentFrontBack) { + // measure signal: average and noise + const float FILTER_TIMESCALE = 0.5f * (float)NSECS_PER_SECOND; + float del = deltaTime.count() / FILTER_TIMESCALE; + if (del > 1.0f) { + del = 1.0f; // clamp for stability + } + _measuredTimeAverage = (1.0f - del) * _measuredTimeAverage + del * measuredTime.count(); + float diff = measuredTime.count() - _measuredTimeAverage; + _measuredTimeNoiseSquared = (1.0f - del) * _measuredTimeNoiseSquared + del * diff * diff; + float noise = sqrtf(_measuredTimeNoiseSquared); + + // check budget + float budgetDelta = _budget.count() - _measuredTimeAverage; + if (abs(budgetDelta) < noise) { + // budget is within the noise + return currentFrontBack; + } + + // clamp the step factor + del = budgetDelta < 0.0f ? -_relativeStepDown: _relativeStepUp; + + return currentFrontBack * (1.0f + del); } glm::vec2 Regulator::clamp(const glm::vec2& backFront) const { @@ -128,17 +153,24 @@ glm::vec2 Regulator::clamp(const glm::vec2& backFront) const { } void ControlViews::regulateViews(workload::Views& outViews, const workload::Timings& timings) { - for (auto& outView : outViews) { for (int32_t r = 0; r < workload::Region::NUM_VIEW_REGIONS; r++) { outView.regionBackFronts[r] = regionBackFronts[r]; } } - auto loopDuration = std::chrono::nanoseconds{ std::chrono::milliseconds(16) }; - regionBackFronts[workload::Region::R1] = regionRegulators[workload::Region::R1].run(loopDuration, timings[0], regionBackFronts[workload::Region::R1]); - regionBackFronts[workload::Region::R2] = regionRegulators[workload::Region::R2].run(loopDuration, timings[0], regionBackFronts[workload::Region::R2]); - regionBackFronts[workload::Region::R3] = regionRegulators[workload::Region::R3].run(loopDuration, timings[1], regionBackFronts[workload::Region::R3]); + // Note: for reference: + // timings[0] = prePhysics entities + // timings[1] = prePhysics avatars + // timings[2] = stepPhysics + // timings[3] = postPhysics + // timings[4] = non-physical kinematics + // timings[5] = game loop + + auto loopDuration = timings[5]; + regionBackFronts[workload::Region::R1] = regionRegulators[workload::Region::R1].run(loopDuration, timings[2] + timings[3], regionBackFronts[workload::Region::R1]); + regionBackFronts[workload::Region::R2] = regionRegulators[workload::Region::R2].run(loopDuration, timings[2] + timings[3], regionBackFronts[workload::Region::R2]); + regionBackFronts[workload::Region::R3] = regionRegulators[workload::Region::R3].run(loopDuration, timings[4], regionBackFronts[workload::Region::R3]); enforceRegionContainment(); for (auto& outView : outViews) { diff --git a/libraries/workload/src/workload/ViewTask.h b/libraries/workload/src/workload/ViewTask.h index 4fdd7e0f4c..7c1e4944f5 100644 --- a/libraries/workload/src/workload/ViewTask.h +++ b/libraries/workload/src/workload/ViewTask.h @@ -37,8 +37,8 @@ namespace workload { { 250.0f, 16000.0f } }; - const float RELATIVE_STEP_DOWN = 0.05f; - const float RELATIVE_STEP_UP = 0.04f; + const float RELATIVE_STEP_DOWN = 0.11f; + const float RELATIVE_STEP_UP = 0.09f; class SetupViewsConfig : public Job::Config{ Q_OBJECT @@ -212,20 +212,31 @@ namespace workload { }; struct Regulator { - using Timing_ns = std::chrono::nanoseconds; - Timing_ns _budget{ std::chrono::milliseconds(2) }; glm::vec2 _minRange{ MIN_VIEW_BACK_FRONTS[0] }; glm::vec2 _maxRange{ MAX_VIEW_BACK_FRONTS[0] }; - glm::vec2 _relativeStepDown{ RELATIVE_STEP_DOWN }; glm::vec2 _relativeStepUp{ RELATIVE_STEP_UP }; - + Timing_ns _budget{ std::chrono::milliseconds(2) }; + float _measuredTimeAverage { 0.0f }; + float _measuredTimeNoiseSquared { 0.0f }; Regulator() {} - Regulator(const Timing_ns& budget_ns, const glm::vec2& minRange, const glm::vec2& maxRange, const glm::vec2& relativeStepDown, const glm::vec2& relativeStepUp) : - _budget(budget_ns), _minRange(minRange), _maxRange(maxRange), _relativeStepDown(relativeStepDown), _relativeStepUp(relativeStepUp) {} + Regulator(const Timing_ns& budget_ns, + const glm::vec2& minRange, + const glm::vec2& maxRange, + const glm::vec2& relativeStepDown, + const glm::vec2& relativeStepUp) : + _minRange(minRange), + _maxRange(maxRange), + _relativeStepDown(relativeStepDown), + _relativeStepUp(relativeStepUp), + _budget(budget_ns), + _measuredTimeAverage(budget_ns.count()), + _measuredTimeNoiseSquared(0.0f) + {} - glm::vec2 run(const Timing_ns& regulationDuration, const Timing_ns& measured, const glm::vec2& current); + void setBudget(const Timing_ns& budget) { _budget = budget; } + glm::vec2 run(const Timing_ns& deltaTime, const Timing_ns& measuredTime, const glm::vec2& currentFrontBack); glm::vec2 clamp(const glm::vec2& backFront) const; }; From a134357489173f6c34d9d1a9ee51faf9ba30a0c8 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 29 Aug 2018 11:37:19 -0700 Subject: [PATCH 134/744] fix compile typo --- libraries/workload/src/workload/ViewTask.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/workload/src/workload/ViewTask.cpp b/libraries/workload/src/workload/ViewTask.cpp index b05a4c0c5f..27b136d64e 100644 --- a/libraries/workload/src/workload/ViewTask.cpp +++ b/libraries/workload/src/workload/ViewTask.cpp @@ -142,9 +142,9 @@ glm::vec2 Regulator::run(const Timing_ns& deltaTime, const Timing_ns& measuredTi } // clamp the step factor - del = budgetDelta < 0.0f ? -_relativeStepDown: _relativeStepUp; + glm::vec2 stepDelta = budgetDelta < 0.0f ? -_relativeStepDown : _relativeStepUp; - return currentFrontBack * (1.0f + del); + return currentFrontBack * (1.0f + stepDelta); } glm::vec2 Regulator::clamp(const glm::vec2& backFront) const { From 6cc31e7397c8de9361f44febc3aa91278f4730d2 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 29 Aug 2018 13:30:13 -0700 Subject: [PATCH 135/744] AVX2 implementation of biquad2_4x4() --- libraries/audio/src/AudioHRTF.cpp | 6 ++- libraries/audio/src/avx2/AudioHRTF_avx2.cpp | 45 +++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index 7895a70595..0f87df2346 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -457,8 +457,12 @@ static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, f interleave_4x4_SSE(src0, src1, src2, src3, dst, numFrames); } +void biquad2_4x4_AVX2(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames); + static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { - biquad2_4x4_SSE(src, dst, coef, state, numFrames); + + static auto f = cpuSupportsAVX2() ? biquad2_4x4_AVX2 : biquad2_4x4_SSE; + (*f)(src, dst, coef, state, numFrames); // dispatch } static void crossfade_4x2(float* src, float* dst, const float* win, int numFrames) { diff --git a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp index 501f010236..0c3c6b5096 100644 --- a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp +++ b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp @@ -87,4 +87,49 @@ void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3 _mm256_zeroupper(); } +// process 2 cascaded biquads on 4 channels (interleaved) +// biquads are computed in parallel, by adding one sample of delay +void biquad2_4x4_AVX2(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { + + // enable flush-to-zero mode to prevent denormals + unsigned int ftz = _MM_GET_FLUSH_ZERO_MODE(); + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); + + // restore state + __m256 x0 = _mm256_setzero_ps(); + __m256 y0 = _mm256_loadu_ps(state[0]); + __m256 w1 = _mm256_loadu_ps(state[1]); + __m256 w2 = _mm256_loadu_ps(state[2]); + + // biquad coefs + __m256 b0 = _mm256_loadu_ps(coef[0]); + __m256 b1 = _mm256_loadu_ps(coef[1]); + __m256 b2 = _mm256_loadu_ps(coef[2]); + __m256 a1 = _mm256_loadu_ps(coef[3]); + __m256 a2 = _mm256_loadu_ps(coef[4]); + + for (int i = 0; i < numFrames; i++) { + + // x0 = (first biquad output << 128) | input + x0 = _mm256_insertf128_ps(_mm256_permute2f128_ps(y0, y0, 0x01), _mm_loadu_ps(&src[4*i]), 0); + + // transposed Direct Form II + y0 = _mm256_fmadd_ps(x0, b0, w1); + w1 = _mm256_fmadd_ps(x0, b1, w2); + w2 = _mm256_mul_ps(x0, b2); + w1 = _mm256_fnmadd_ps(y0, a1, w1); + w2 = _mm256_fnmadd_ps(y0, a2, w2); + + _mm_storeu_ps(&dst[4*i], _mm256_extractf128_ps(y0, 1)); // second biquad output + } + + // save state + _mm256_storeu_ps(state[0], y0); + _mm256_storeu_ps(state[1], w1); + _mm256_storeu_ps(state[2], w2); + + _MM_SET_FLUSH_ZERO_MODE(ftz); + _mm256_zeroupper(); +} + #endif From 0981f08b22c5f04094428a84d8faeddbdf8b3347 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 29 Aug 2018 14:48:27 -0700 Subject: [PATCH 136/744] Do not propagate avatar scale in AvatarHeadTransformNode --- interface/src/avatar/MyAvatarHeadTransformNode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatarHeadTransformNode.cpp b/interface/src/avatar/MyAvatarHeadTransformNode.cpp index 82c2f8b703..9c202ba94a 100644 --- a/interface/src/avatar/MyAvatarHeadTransformNode.cpp +++ b/interface/src/avatar/MyAvatarHeadTransformNode.cpp @@ -19,5 +19,5 @@ Transform MyAvatarHeadTransformNode::getTransform() { glm::quat headOri = myAvatar->getHeadOrientation(); glm::quat ori = headOri * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT); - return Transform(ori, myAvatar->scaleForChildren(), pos); + return Transform(ori, glm::vec3(1.0f), pos); } \ No newline at end of file From 78a438d89a8da8d0a7260c1ac9bb510b27803359 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 29 Aug 2018 14:55:14 -0700 Subject: [PATCH 137/744] Correct CollisionRegion jsdoc for 'joint' parameter --- libraries/shared/src/RegisteredMetaTypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 32ecfe8b6d..8fefc37741 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -263,7 +263,7 @@ public: * @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region. * @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay. * @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) -* @property {string} joint - If "Mouse," parents the pick to the mouse. If "Head," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. +* @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. */ class CollisionRegion : public MathPick { public: From 2d73b7845e95257bd40bf725eb597f28ba90de8f Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 29 Aug 2018 15:00:01 -0700 Subject: [PATCH 138/744] Code style fixes --- interface/src/raypick/CollisionPick.cpp | 3 +-- interface/src/raypick/CollisionPick.h | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 407595b86a..091ceb0794 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -94,8 +94,7 @@ bool CollisionPick::getShapeInfoReady() { } return false; - } - else { + } else { computeShapeInfoDimensionsOnly(_mathPick, *_mathPick.shapeInfo, _cachedResource); return true; } diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 7ff3670e53..f46f0742f5 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -25,11 +25,7 @@ public: CollisionPickResult() {} CollisionPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} - CollisionPickResult(const CollisionRegion& searchRegion, - LoadState loadState, - const std::vector& entityIntersections, - const std::vector& avatarIntersections - ) : + CollisionPickResult(const CollisionRegion& searchRegion, LoadState loadState, const std::vector& entityIntersections, const std::vector& avatarIntersections) : PickResult(searchRegion.toVariantMap()), loadState(loadState), intersects(entityIntersections.size() || avatarIntersections.size()), From ed4dbaa4c71de483334c26808053ec98d59955e5 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Wed, 29 Aug 2018 16:07:30 -0700 Subject: [PATCH 139/744] Refining the plot widget to be able to represent negative values --- interface/src/LODManager.cpp | 16 +++++- interface/src/LODManager.h | 12 +++- .../utilities/lib/plotperf/PlotPerf.qml | 56 ++++++++++++++++--- scripts/developer/utilities/render/lod.qml | 16 +++++- 4 files changed, 87 insertions(+), 13 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index a32f3472ab..ce674f7295 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -72,6 +72,11 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { // Note: we MUST clamp the blend to 1.0 for stability float blend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * maxRenderTime; // msec + + float smoothBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE * _pidCoefs.w) ? realTimeDelta / (LOD_ADJUST_RUNNING_AVG_TIMESCALE * _pidCoefs.w) : 1.0f; + _smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec + + // _avgRenderTime = maxRenderTime; if (!_automaticLODAdjust || _avgRenderTime == 0.0f) { // early exit @@ -82,13 +87,17 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { float oldSolidAngle = getLODAngleDeg(); float targetFPS = 0.5 * (getLODDecreaseFPS() + getLODIncreaseFPS()); + // float targetFPS = (getLODDecreaseFPS()); float targetPeriod = 1.0f / targetFPS; float currentFPS = (float)MSECS_PER_SECOND / _avgRenderTime; + static uint64_t lastTime = usecTimestampNow(); + uint64_t now = usecTimestampNow(); auto dt = (float) ((now - lastTime) / double(USECS_PER_MSEC)); - if (dt < targetPeriod * _pidCoefs.w) return; + // if (dt < targetPeriod * _pidCoefs.w) return; + dt = realTimeDelta; lastTime = now; auto previous_error = _pidHistory.x; @@ -96,7 +105,12 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { auto error = (targetFPS - currentFPS) / targetFPS; error = glm::clamp(error, -1.0f, 1.0f); + if (error <= 0.0f) { + // error = error * 2.0f; + } auto integral = previous_integral + error * dt; + glm::clamp(integral, -1.0f, 1.0f); + auto derivative = (error - previous_error) / dt; _pidHistory.x = error; diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 74075f2400..167533a236 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -61,8 +61,13 @@ class LODManager : public QObject, public Dependency { Q_PROPERTY(float engineRunTime READ getEngineRunTime) Q_PROPERTY(float batchTime READ getBatchTime) Q_PROPERTY(float gpuTime READ getGPUTime) + Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) Q_PROPERTY(float fps READ getMaxTheoreticalFPS) + + Q_PROPERTY(float smoothRenderTime READ getSmoothRenderTime) + Q_PROPERTY(float smoothFPS READ getSmoothFPS) + Q_PROPERTY(float lodLevel READ getLODLevel WRITE setLODLevel NOTIFY LODChanged) Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) @@ -193,6 +198,10 @@ public: float getAverageRenderTime() const { return _avgRenderTime; }; float getMaxTheoreticalFPS() const { return (float)MSECS_PER_SECOND / _avgRenderTime; }; + float getSmoothRenderTime() const { return _smoothRenderTime; }; + float getSmoothFPS() const { return (float)MSECS_PER_SECOND / _smoothRenderTime; }; + + float getLODLevel() const; void setLODLevel(float level); @@ -246,13 +255,14 @@ private: float _gpuTime { 0.0f }; // msec float _avgRenderTime { 0.0f }; // msec + float _smoothRenderTime{ 0.0f }; float _desktopMaxRenderTime { DEFAULT_DESKTOP_MAX_RENDER_TIME }; float _hmdMaxRenderTime { DEFAULT_HMD_MAX_RENDER_TIME }; float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; - glm::vec4 _pidCoefs{ 4.0f, 0.0000000f, 0.f, 4.0f }; + glm::vec4 _pidCoefs{ 1.0f, 0.0000000f, 0.f, 8.0f }; glm::vec4 _pidHistory{ 0.0f }; glm::vec4 _pidOutputs{ 0.0f }; diff --git a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml index 99ff5f712e..46056be5fd 100644 --- a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml +++ b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml @@ -50,6 +50,10 @@ Item { property var valueMax : 1 property var valueMin : 0 + property var displayMinAt0 : true + property var _displayMaxValue : 1 + property var _displayMinValue : 0 + property var _values property var tick : 0 @@ -72,7 +76,9 @@ Item { value: value, fromBinding: isBinding, valueMax: 1, + valueMin: 0, numSamplesConstantMax: 0, + numSamplesConstantMin: 0, valueHistory: new Array(), label: (plot["label"] !== undefined ? plot["label"] : ""), color: (plot["color"] !== undefined ? plot["color"] : "white"), @@ -129,7 +135,7 @@ Item { _values[i].valueMax = currentVal; _values[i].numSamplesConstantMax = 0 } - if (_values[i].valueMin < currentVal) { + if (_values[i].valueMin > currentVal) { _values[i].valueMin = currentVal; _values[i].numSamplesConstantMin = 0 } @@ -146,7 +152,7 @@ Item { if (currentValueMax < _values[i].valueMax) { currentValueMax = _values[i].valueMax } - if (currentValueMin < _values[i].valueMin) { + if (currentValueMin > _values[i].valueMin) { currentValueMin = _values[i].valueMin } } @@ -154,9 +160,12 @@ Item { if ((valueMax < currentValueMax) || (tick % VALUE_HISTORY_SIZE == 0)) { valueMax = currentValueMax; } - if ((valueMin < currentValueMin) || (tick % VALUE_HISTORY_SIZE == 0)) { + if ((valueMin > currentValueMin) || (tick % VALUE_HISTORY_SIZE == 0)) { valueMin = currentValueMin; } + _displayMaxValue = valueMax; + _displayMinValue = ( displayMinAt0 ? 0 : valueMin ) + mycanvas.requestPaint() } @@ -177,10 +186,10 @@ Item { } function pixelFromVal(val, valScale) { - return lineHeight + (height - lineHeight) * (1 - (0.9) * val / valueMax); + return lineHeight + (height - lineHeight) * (1 - (0.9) * (val - _displayMinValue) / (_displayMaxValue - _displayMinValue)); } function valueFromPixel(pixY) { - return ((pixY - lineHeight) / (height - lineHeight) - 1) * valueMax / (-0.9); + return _displayMinValue + (((pixY - lineHeight) / (height - lineHeight) - 1) * (_displayMaxValue - _displayMinValue) / (-0.9)); } function plotValueHistory(ctx, valHistory, color) { var widthStep= width / (valHistory.length - 1); @@ -208,8 +217,10 @@ Item { function displayTitle(ctx, text, maxVal) { ctx.fillStyle = "grey"; ctx.textAlign = "right"; - ctx.fillText(displayValue(valueFromPixel(lineHeight), root.valueUnit), width, lineHeight); + ctx.fillText("max" + displayValue(_displayMaxValue, root.valueUnit), width, pixelFromVal(_displayMaxValue)); + ctx.fillText("min" + displayValue(_displayMinValue, root.valueUnit), width, pixelFromVal(_displayMinValue)); + ctx.fillStyle = "white"; ctx.textAlign = "left"; ctx.fillText(text, 0, lineHeight); @@ -218,15 +229,39 @@ Item { ctx.fillStyle = Qt.rgba(0, 0, 0, root.backgroundOpacity); ctx.fillRect(0, 0, width, height); - ctx.strokeStyle= "grey"; + /* ctx.strokeStyle= "grey"; ctx.lineWidth="2"; ctx.beginPath(); ctx.moveTo(0, lineHeight + 1); - ctx.lineTo(width, lineHeight + 1); + ctx.lineTo(width, lineHeight + 1); ctx.moveTo(0, height); ctx.lineTo(width, height); + ctx.stroke();*/ + } + + function displayMaxZeroMin(ctx) { + var maxY = pixelFromVal(_displayMaxValue); + + ctx.strokeStyle= "LightSlateGray"; + ctx.lineWidth="1"; + // ctx.strokeStyle= "grey"; + ctx.beginPath(); + ctx.moveTo(0, maxY); + ctx.lineTo(width, maxY); ctx.stroke(); + + if (_displayMinValue != 0) { + var zeroY = pixelFromVal(0); + var minY = pixelFromVal(_displayMinValue); + // ctx.strokeStyle= "DarkRed"; + ctx.beginPath(); + ctx.moveTo(0, zeroY); + ctx.lineTo(width, zeroY); + ctx.moveTo(0, minY); + ctx.lineTo(width, minY); + ctx.stroke(); + } } var ctx = getContext("2d"); @@ -240,7 +275,9 @@ Item { displayValueLegend(ctx, _values[i], i) } - displayTitle(ctx, title, valueMax) + displayMaxZeroMin(ctx); + + displayTitle(ctx, title, _displayMaxValue) } } @@ -250,6 +287,7 @@ Item { onClicked: { resetMax(); + resetMin(); } } } diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index fe284fec93..d0562e1020 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -99,7 +99,7 @@ Item { label: "LOD PID Ki" valueVar: LODManager["pidKi"] valueVarSetter: (function (v) { LODManager["pidKi"] = v }) - max: 0.000005 + max: 0.1 min: 0.0 integral: false numDigits: 8 @@ -165,6 +165,11 @@ Item { label: "present", color: "#FFFF00" }, + { + prop: "batchTime", + label: "batch", + color: "#00FF00" + }, { prop: "engineRunTime", label: "engineRun", @@ -192,7 +197,12 @@ Item { { prop: "fps", label: "FPS", - color: "#FFFFFF" + color: "#FFFF55" + }, + { + prop: "smoothFPS", + label: "Smooth FPS", + color: "#9999FF" }, { prop: "lodDecreaseFPS", @@ -221,6 +231,8 @@ Item { object: LODManager valueScale: 1.0 valueUnit: "deg" + valueNumDigits: 1 + displayMinAt0: false plots: [ { prop: "pidOp", From 6cdcae33496f20c6c9096648eb169e88f49ad959 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 29 Aug 2018 16:11:51 -0700 Subject: [PATCH 140/744] switching address syntax, updating redirection domain --- interface/CMakeLists.txt | 2 - .../qml/hifi/tablet/TabletAddressDialog.qml | 4 +- interface/resources/serverless/redirect.json | 1155 ++++++++++++++++- 3 files changed, 1156 insertions(+), 5 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index cd058add94..ded77b9013 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -341,8 +341,6 @@ else() set(INTERFACE_EXEC_DIR "$") set(RESOURCES_DEV_DIR "${INTERFACE_EXEC_DIR}/resources") - message(STATUS "${RESOURCES_DEV_DIR}") - # copy the resources files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index c9d05aea51..bec3d82c56 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -79,7 +79,7 @@ StackView { return; } location.text = targetString; - toggleOrGo(true, targetString); + toggleOrGo(targetString, true); clearAddressLineTimer.start(); } @@ -401,7 +401,7 @@ StackView { } } - function toggleOrGo(fromSuggestions, address) { + function toggleOrGo(address, fromSuggestions) { if (address !== undefined && address !== "") { addressBarDialog.loadAddress(address, fromSuggestions); clearAddressLineTimer.start(); diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index d97f44220a..9e3b29cc26 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -1 +1,1154 @@ -{"DataVersion": 0, "Entities": [{"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{9a50eca4-5c97-4e21-8cb9-5197bb09770e}", "lastEdited": 1535474935077402, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{2d90745c-2fe2-4f9c-9dcf-b715f7a9d0c0}", "lastEdited": 1535474935076550, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{dcbca0fd-e1d8-4248-b2b4-b91346224876}", "lastEdited": 1535474935077745, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.927593469619751, "x": -3.072406530380249, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.479954719543457}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{3df15ffb-9614-4ec1-81a8-e278d1917748}", "lastEdited": 1535474935076889, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{fd138f98-d436-41d2-863a-cbef6e41e1cb}", "lastEdited": 1535474935078215, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{b2a90f9e-a5ec-4faa-a6a3-bc457516f5ee}", "lastEdited": 1535474935077505, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{5c9ef440-f022-4d21-9f00-22827851b522}", "lastEdited": 1535474935077201, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.948914527893066, "green": 0.16741032898426056, "red": 0.9275782108306885, "x": -3.0724217891693115, "y": -9.33258967101574, "z": 1.9489145278930664}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904473781585693, "y": 1.028862476348877, "z": -2.479969024658203}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{2fe2296a-d74d-4ee7-bf7d-c9665bc63237}", "lastEdited": 1535474935076656, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{86f1ba10-2921-45ad-bb74-dcbe5c49bc69}", "lastEdited": 1535474935077298, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{1023380c-13dd-445d-bb57-1e02574e5cb3}", "lastEdited": 1535474935076124, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{d5d841b2-0cc0-4ab2-84fe-dce3d20f6b04}", "lastEdited": 1535474935077609, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.927593469619751, "x": -3.072406530380249, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.479954719543457}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{02da6c73-bab0-4e7e-b8fc-a9c0500ac66e}", "lastEdited": 1535474935075994, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{f1c2110e-1429-49b2-949d-6c73718b2d65}", "lastEdited": 1535474935078059, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{1e09084f-c0d7-4bd4-a78d-290c03391579}", "lastEdited": 1535474935076234, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.927593469619751, "x": -3.072406530380249, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.479954719543457}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{e8282a17-76a5-4cd8-9f0e-473f097c8ad1}", "lastEdited": 1535474935077902, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{46fbad8a-5bc0-44c8-a9ca-beb38f672d2a}", "lastEdited": 1535474935076994, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{2861b544-1c03-439a-85ad-3758ba87ca55}", "lastEdited": 1535474935076339, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{59b7fc7f-0100-4115-a9f8-55d5b45da540}", "lastEdited": 1535474935077098, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{2888e4b0-6eb1-4468-9b95-893dcb3e99a5}", "lastEdited": 1535474935076443, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"emissiveMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel_highlight.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": true, "created": "2018-08-28T16:44:23Z", "id": "{36f00cad-64b0-415f-9f70-e92cd85836d8}", "lastEdited": 1535474935076783, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "materialData": "{\"materials\":{\"albedoMap\":\"file:///C:/users/wayne/development/hifi-fork/interface/resources/images/buttonBezel.png\"}}", "materialURL": "materialData", "owningAvatarID": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "parentID": "{00000000-0000-0000-0000-000000000001}", "parentMaterialName": "2", "position": {"blue": 5.94891357421875, "green": 0, "red": 0.9275937080383301, "x": -3.07240629196167, "y": -9.5, "z": 1.94891357421875}, "priority": 1, "queryAACube": {"scale": 0.5196152329444885, "x": -5.904480457305908, "y": 0.8614521026611328, "z": -2.4799537658691406}, "type": "Material", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": false, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 14.40000057220459, "green": 14.40000057220459, "red": 14.40000057220459, "x": 14.40000057220459, "y": 14.40000057220459, "z": 14.40000057220459}, "id": "{2cefd6b6-6a00-49c3-87c8-71b9c83f96f1}", "lastEdited": 1535474935056115, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 2.3440732955932617, "green": 1.7684326171875, "red": 1.8812973499298096, "x": -2.1187026500701904, "y": -7.7315673828125, "z": -1.6559267044067383}, "queryAACube": {"scale": 24.9415340423584, "x": -10.589469909667969, "y": -10.7023344039917, "z": -10.126693725585938}, "rotation": {"w": 0.8697794675827026, "x": -1.52587890625e-05, "y": 0.4933699369430542, "z": -4.57763671875e-05}, "shapeType": "box", "skyboxMode": "enabled", "type": "Zone", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"clientOnly": false, "color": {"blue": 0, "green": 0, "red": 0}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 11.117486953735352, "green": 3.580313205718994, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 3.580313205718994, "z": 11.117486953735352}, "id": "{efde7819-3a7a-4984-ac3e-28bcf69c6b1e}", "lastEdited": 1535474935053285, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 0, "green": 1.1583251953125, "red": 4.971565246582031, "x": 0.9715652465820312, "y": -8.3416748046875, "z": -4}, "queryAACube": {"scale": 11.681488037109375, "x": -0.8691787719726562, "y": -4.6824188232421875, "z": -5.8407440185546875}, "rotation": {"w": 0.8637980222702026, "x": -4.57763671875e-05, "y": 0.5038070678710938, "z": -1.52587890625e-05}, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": false, "color": {"blue": 0, "green": 0, "red": 0}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 11.117486953735352, "green": 3.580313205718994, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 3.580313205718994, "z": 11.117486953735352}, "id": "{fe8d6b12-3697-4fcd-9092-af5b7f49a7f5}", "lastEdited": 1535474947253763, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 5.268576622009277, "green": 1.1588134765625, "red": 6.100250244140625, "x": 2.100250244140625, "y": -8.3411865234375, "z": 1.2685766220092773}, "queryAACube": {"scale": 11.681488037109375, "x": 0.2595062255859375, "y": -4.6819305419921875, "z": -0.5721673965454102}, "rotation": {"w": 0.9662165641784668, "x": -4.57763671875e-05, "y": -0.2576791048049927, "z": 1.52587890625e-05}, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": false, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.06014331430196762, "green": 2.582186460494995, "red": 2.582186698913574, "x": 2.582186698913574, "y": 2.582186460494995, "z": 0.06014331430196762}, "id": "{c8f9b1b1-3bd7-473c-8c2a-a8644a7acdd7}", "lastEdited": 1535474935054058, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/oopsDialog.fbx", "name": "Oops Dialog", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 1.45927095413208, "green": 1.6763916015625, "red": 0, "x": -4, "y": -7.8236083984375, "z": -2.54072904586792}, "queryAACube": {"scale": 3.6522583961486816, "x": -1.8261291980743408, "y": -0.14973759651184082, "z": -0.36685824394226074}, "rotation": {"w": 0.8684672117233276, "x": -4.57763671875e-05, "y": 0.4957197904586792, "z": -7.62939453125e-05}, "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"angularDamping": 0, "clientOnly": false, "color": {"blue": 0, "green": 0, "red": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.20000000298023224, "green": 0.20000000298023224, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 0.20000000298023224, "z": 0.20000000298023224}, "id": "{d0b0491a-5c7d-4cd2-9ebe-492b709a702b}", "lastEdited": 1535474935052912, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 3.8216662406921387, "green": 0, "red": 1.2409718036651611, "x": -2.759028196334839, "y": -9.5, "z": -0.17833375930786133}, "queryAACube": {"scale": 0.3464101552963257, "x": 1.0677666664123535, "y": -0.17320507764816284, "z": 3.648461103439331}, "rotation": {"w": 0.6444342136383057, "x": -0.08220034837722778, "y": -0.6649118661880493, "z": 0.3684900999069214}, "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"angularDamping": 0, "clientOnly": false, "color": {"blue": 0, "green": 0, "red": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.20000000298023224, "green": 0.20000000298023224, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 0.20000000298023224, "z": 0.20000000298023224}, "id": "{ae4a84ee-4e95-4e6b-b63d-399893a9b3f9}", "lastEdited": 1535474935052114, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 1.3116319179534912, "green": 0, "red": 2.705613613128662, "x": -1.294386386871338, "y": -9.5, "z": -2.688368082046509}, "queryAACube": {"scale": 0.3464101552963257, "x": 2.5324084758758545, "y": -0.17320507764816284, "z": 1.1384267807006836}, "rotation": {"w": 0.9127794504165649, "x": 0.2575265169143677, "y": 0.15553522109985352, "z": 0.2761729955673218}, "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"angularDamping": 0, "clientOnly": false, "color": {"blue": 0, "green": 0, "red": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.20000000298023224, "green": 0.20000000298023224, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 0.20000000298023224, "z": 0.20000000298023224}, "id": "{c1404297-9c6a-4d2d-b91d-c844ddd391db}", "lastEdited": 1535474935051740, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 3.8216662406921387, "green": 0.0772705078125, "red": 1.2409718036651611, "x": -2.759028196334839, "y": -9.4227294921875, "z": -0.17833375930786133}, "queryAACube": {"scale": 0.3464101552963257, "x": 1.0677666664123535, "y": -0.09593456983566284, "z": 3.648461103439331}, "rotation": {"w": 0.926024317741394, "x": 0.20308232307434082, "y": -0.010269343852996826, "z": 0.3179827928543091}, "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": false, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 9.030570983886719, "green": 0.0719478651881218, "red": 9.030570983886719, "x": 9.030570983886719, "y": 0.0719478651881218, "z": 9.030570983886719}, "id": "{f17920f3-1571-4c5e-b002-e328cf202037}", "lastEdited": 1535474950564649, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/floor.fbx", "name": "Floor", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 4.2324604988098145, "green": 0.17547607421875, "red": 4.802988529205322, "x": 0.8029885292053223, "y": -9.32452392578125, "z": 0.23246049880981445}, "queryAACube": {"scale": 12.771358489990234, "x": -1.582690715789795, "y": -6.210203170776367, "z": -2.1532187461853027}, "rotation": {"w": 0.8648051023483276, "x": -1.52587890625e-05, "y": 0.5020675659179688, "z": -4.57763671875e-05}, "shapeType": "simple-hull", "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"alpha": 0, "alphaFinish": 0, "alphaStart": 0.25, "clientOnly": false, "colorFinish": {"blue": 0, "green": 0, "red": 0, "x": 0, "y": 0, "z": 0}, "colorStart": {"blue": 255, "green": 255, "red": 255, "x": 255, "y": 255, "z": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 13.24000072479248, "green": 13.24000072479248, "red": 13.24000072479248, "x": 13.24000072479248, "y": 13.24000072479248, "z": 13.24000072479248}, "emitAcceleration": {"blue": 0, "green": 0.10000000149011612, "red": 0, "x": 0, "y": 0.10000000149011612, "z": 0}, "emitDimensions": {"blue": 1, "green": 1, "red": 1, "x": 1, "y": 1, "z": 1}, "emitOrientation": {"w": 1, "x": -1.52587890625e-05, "y": -1.52587890625e-05, "z": -1.52587890625e-05}, "emitRate": 6, "emitSpeed": 0, "id": "{7561445a-4f9c-41aa-96b9-b8ce8ad686d3}", "lastEdited": 1535474935056499, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "lifespan": 10, "maxParticles": 10, "name": "Stars", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "particleRadius": 0.07000000029802322, "polarFinish": 3.1415927410125732, "position": {"blue": 1.3712034225463867, "green": 0.5220947265625, "red": 2.6281180381774902, "x": -1.3718819618225098, "y": -8.9779052734375, "z": -2.6287965774536133}, "queryAACube": {"scale": 22.932353973388672, "x": -8.838058471679688, "y": -10.944082260131836, "z": -10.09497356414795}, "radiusFinish": 0, "radiusStart": 0, "rotation": {"w": 0.9852597713470459, "x": -1.52587890625e-05, "y": -0.17106890678405762, "z": -7.62939453125e-05}, "speedSpread": 0, "spinFinish": null, "spinStart": null, "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"alpha": 0, "alphaFinish": 0, "alphaStart": 1, "clientOnly": false, "color": {"blue": 255, "green": 205, "red": 3}, "colorFinish": {"blue": 0, "green": 0, "red": 0, "x": 0, "y": 0, "z": 0}, "colorStart": {"blue": 255, "green": 204, "red": 0, "x": 0, "y": 204, "z": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 2.5, "green": 2.5, "red": 2.5, "x": 2.5, "y": 2.5, "z": 2.5}, "emitAcceleration": {"blue": 0, "green": 0, "red": 0, "x": 0, "y": 0, "z": 0}, "emitDimensions": {"blue": 1, "green": 1, "red": 1, "x": 1, "y": 1, "z": 1}, "emitOrientation": {"w": 0.9993909597396851, "x": 0.034897372126579285, "y": -1.525880907138344e-05, "z": -1.525880907138344e-05}, "emitRate": 2, "emitSpeed": 0, "emitterShouldTrail": true, "id": "{7c59b77f-2f88-4c35-899b-3e42bd486ed6}", "lastEdited": 1535474935050547, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "lifespan": 10, "maxParticles": 40, "name": "Rays", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "particleRadius": 0.75, "polarFinish": 3.1415927410125732, "position": {"blue": 3.814434051513672, "green": 1.44122314453125, "red": 1.2319090366363525, "x": -2.7680909633636475, "y": -8.05877685546875, "z": -0.18556594848632812}, "queryAACube": {"scale": 4.330127239227295, "x": -0.9331545829772949, "y": -0.7238404750823975, "z": 1.6493704319000244}, "radiusFinish": 0.10000000149011612, "radiusStart": 0, "rotation": {"w": 0.9594720602035522, "x": -1.52587890625e-05, "y": 0.28178834915161133, "z": -4.57763671875e-05}, "speedSpread": 0, "spinFinish": null, "spinStart": null, "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"angularDamping": 0, "clientOnly": false, "color": {"blue": 0, "green": 0, "red": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.20000000298023224, "green": 0.20000000298023224, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 0.20000000298023224, "z": 0.20000000298023224}, "id": "{55dd2b28-1c53-431b-b2ea-61155dfc6652}", "lastEdited": 1535474935055239, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 1.3116319179534912, "green": 0.15618896484375, "red": 2.705613613128662, "x": -1.294386386871338, "y": -9.34381103515625, "z": -2.688368082046509}, "queryAACube": {"scale": 0.3464101552963257, "x": 2.5324084758758545, "y": -0.017016112804412842, "z": 1.1384267807006836}, "rotation": {"w": 0.46953535079956055, "x": -0.16719311475753784, "y": -0.7982757091522217, "z": 0.3380941152572632}, "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"angularDamping": 0, "clientOnly": false, "color": {"blue": 0, "green": 0, "red": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.20000000298023224, "green": 0.20000000298023224, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 0.20000000298023224, "z": 0.20000000298023224}, "id": "{f40563f7-01e3-4faf-ad1b-19cab7a386a1}", "lastEdited": 1535474935054426, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 1.3116319179534912, "green": 0.0772705078125, "red": 2.705613613128662, "x": -1.294386386871338, "y": -9.4227294921875, "z": -2.688368082046509}, "queryAACube": {"scale": 0.3464101552963257, "x": 2.5324084758758545, "y": -0.09593456983566284, "z": 1.1384267807006836}, "rotation": {"w": -0.38777750730514526, "x": -0.37337303161621094, "y": -0.8409399390220642, "z": 0.055222392082214355}, "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"alpha": 0, "alphaFinish": 0, "alphaStart": 1, "clientOnly": false, "color": {"blue": 211, "green": 227, "red": 104}, "colorFinish": {"blue": 0, "green": 0, "red": 0, "x": 0, "y": 0, "z": 0}, "colorStart": {"blue": 211, "green": 227, "red": 104, "x": 104, "y": 227, "z": 211}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 2.5, "green": 2.5, "red": 2.5, "x": 2.5, "y": 2.5, "z": 2.5}, "emitAcceleration": {"blue": 0, "green": 0, "red": 0, "x": 0, "y": 0, "z": 0}, "emitDimensions": {"blue": 1, "green": 1, "red": 1, "x": 1, "y": 1, "z": 1}, "emitOrientation": {"w": 0.9993909597396851, "x": 0.034897372126579285, "y": -1.525880907138344e-05, "z": -1.525880907138344e-05}, "emitRate": 2, "emitSpeed": 0, "id": "{c4a86707-2eaa-41f5-a1ad-3e6029178d75}", "lastEdited": 1535474935052538, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "lifespan": 10, "maxParticles": 40, "name": "Rays", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "particleRadius": 0.75, "polarFinish": 3.1415927410125732, "position": {"blue": 1.3553659915924072, "green": 1.44122314453125, "red": 2.572803497314453, "x": -1.4271965026855469, "y": -8.05877685546875, "z": -2.6446340084075928}, "queryAACube": {"scale": 4.330127239227295, "x": 0.40773987770080566, "y": -0.7238404750823975, "z": -0.8096976280212402}, "radiusFinish": 0.10000000149011612, "radiusStart": 0, "rotation": {"w": 0.9803768396377563, "x": -1.52587890625e-05, "y": 0.19707024097442627, "z": -7.62939453125e-05}, "speedSpread": 0, "spinFinish": null, "spinStart": null, "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"angularDamping": 0, "clientOnly": false, "color": {"blue": 0, "green": 0, "red": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 0.20000000298023224, "green": 0.20000000298023224, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 0.20000000298023224, "z": 0.20000000298023224}, "id": "{65dd9c7f-815d-4b50-ba3b-24709691aed1}", "lastEdited": 1535474935056805, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 3.8216662406921387, "green": 0.15618896484375, "red": 1.2409718036651611, "x": -2.759028196334839, "y": -9.34381103515625, "z": -0.17833375930786133}, "queryAACube": {"scale": 0.3464101552963257, "x": 1.0677666664123535, "y": -0.017016112804412842, "z": 3.648461103439331}, "rotation": {"w": 0.6747081279754639, "x": -0.06532388925552368, "y": -0.6342412233352661, "z": 0.37175559997558594}, "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": false, "color": {"blue": 0, "green": 0, "red": 0}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 11.117486953735352, "green": 3.580313205718994, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 3.580313205718994, "z": 11.117486953735352}, "id": "{4e0f3381-ac88-48ab-9378-0b7818188234}", "lastEdited": 1535474935053657, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 6.1806135177612305, "green": 1.1588134765625, "red": 1.4755167961120605, "x": -2.5244832038879395, "y": -8.3411865234375, "z": 2.1806135177612305}, "queryAACube": {"scale": 11.681488037109375, "x": -4.365227222442627, "y": -4.6819305419921875, "z": 0.33986949920654297}, "rotation": {"w": 0.8637980222702026, "x": -4.57763671875e-05, "y": 0.5038070678710938, "z": -1.52587890625e-05}, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"clientOnly": false, "color": {"blue": 0, "green": 0, "red": 0}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 11.117486953735352, "green": 3.580313205718994, "red": 0.20000000298023224, "x": 0.20000000298023224, "y": 3.580313205718994, "z": 11.117486953735352}, "id": "{2da3a1d6-c757-4cc3-b20c-d7ec87eedd52}", "lastEdited": 1535474935050931, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 1.9063844680786133, "green": 1.15850830078125, "red": 0.16632509231567383, "x": -3.833674907684326, "y": -8.34149169921875, "z": -2.0936155319213867}, "queryAACube": {"scale": 11.681488037109375, "x": -5.674418926239014, "y": -4.6822357177734375, "z": -3.934359550476074}, "rotation": {"w": 0.9666743278503418, "x": -4.57763671875e-05, "y": -0.2560006380081177, "z": 1.52587890625e-05}, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false}, {"alpha": 0, "alphaFinish": 0, "alphaStart": 0.25, "clientOnly": false, "colorFinish": {"blue": 0, "green": 0, "red": 0, "x": 0, "y": 0, "z": 0}, "colorStart": {"blue": 255, "green": 255, "red": 255, "x": 255, "y": 255, "z": 255}, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 13.24000072479248, "green": 13.24000072479248, "red": 13.24000072479248, "x": 13.24000072479248, "y": 13.24000072479248, "z": 13.24000072479248}, "emitAcceleration": {"blue": 0, "green": 0.10000000149011612, "red": 0, "x": 0, "y": 0.10000000149011612, "z": 0}, "emitDimensions": {"blue": 1, "green": 1, "red": 1, "x": 1, "y": 1, "z": 1}, "emitOrientation": {"w": 1, "x": -1.52587890625e-05, "y": -1.52587890625e-05, "z": -1.52587890625e-05}, "emitRate": 6, "emitSpeed": 0, "emitterShouldTrail": true, "id": "{7db57102-9a57-48d6-9937-bcec7892c3d9}", "lastEdited": 1535474935058139, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "lifespan": 10, "maxParticles": 10, "name": "Stars", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "particleRadius": 0.07000000029802322, "polarFinish": 3.1415927410125732, "position": {"blue": 3.78922963142395, "green": 0.5220947265625, "red": 1.1928560733795166, "x": -2.8071439266204834, "y": -8.9779052734375, "z": -0.2107703685760498}, "queryAACube": {"scale": 22.932353973388672, "x": -10.273321151733398, "y": -10.944082260131836, "z": -7.676947593688965}, "radiusFinish": 0, "radiusStart": 0, "rotation": {"w": 0.996429443359375, "x": -1.52587890625e-05, "y": -0.08442819118499756, "z": -4.57763671875e-05}, "speedSpread": 0, "spinFinish": null, "spinStart": null, "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}"}, {"clientOnly": false, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 2.1097896099090576, "green": 0.04847164824604988, "red": 1.458284616470337, "x": 1.458284616470337, "y": 0.04847164824604988, "z": 2.1097896099090576}, "id": "{434940be-0b45-4ca2-97db-91e698c20bfd}", "lastEdited": 1535474935055655, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal2.fbx", "name": "Back", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 1.5835940837860107, "green": 0.2467041015625, "red": 3.0345542430877686, "x": -0.9654457569122314, "y": -9.2532958984375, "z": -2.4164059162139893}, "queryAACube": {"scale": 2.5651814937591553, "x": 1.751963496208191, "y": -1.0358866453170776, "z": 0.3010033369064331}, "rotation": {"w": 0.9084458351135254, "x": -1.52587890625e-05, "y": 0.4179598093032837, "z": -0.0001068115234375}, "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":true}}"}, {"clientOnly": false, "created": "2018-08-28T16:44:23Z", "dimensions": {"blue": 2.1097896099090576, "green": 0.04847164824604988, "red": 1.458284616470337, "x": 1.458284616470337, "y": 0.04847164824604988, "z": 2.1097896099090576}, "id": "{5f853b12-33b9-4f40-92a8-4506047fdcd8}", "lastEdited": 1535474935051356, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal1.fbx", "name": "Try Again", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": {"blue": 3.946338653564453, "green": 0.2467041015625, "red": 1.6013128757476807, "x": -2.3986871242523193, "y": -9.2532958984375, "z": -0.053661346435546875}, "queryAACube": {"scale": 2.5651814937591553, "x": 0.318722128868103, "y": -1.0358866453170776, "z": 2.663747787475586}, "rotation": {"w": 0.8220492601394653, "x": -1.52587890625e-05, "y": 0.5693598985671997, "z": -0.0001068115234375}, "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":true}}"}], "Id": "{aa6032b9-9c35-4481-acd9-03b97400b83a}", "Version": 93} \ No newline at end of file +{ + "DataVersion": 0, + "Entities": [ + { + "clientOnly": false, + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "created": "2018-08-29T22:59:44Z", + "dimensions": { + "blue": 0.26190000772476196, + "green": 0.5595999956130981, + "red": 0.5318999886512756, + "x": 0.5318999886512756, + "y": 0.5595999956130981, + "z": 0.26190000772476196 + }, + "id": "{4782f05d-ca51-41d4-9d19-21b8cad5ec2d}", + "lastEdited": 1535584079688600, + "lastEditedBy": "{f5457c14-32ca-45c2-9dde-4f5b30b3be1b}", + "name": "Try Again Box", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 4.54052734375, + "green": 1.722428798675537, + "red": 4.593362331390381, + "x": 4.593362331390381, + "y": 1.722428798675537, + "z": 4.54052734375 + }, + "queryAACube": { + "scale": 0.8152676820755005, + "x": 4.185728549957275, + "y": 1.3147950172424316, + "z": 4.1328935623168945 + }, + "rotation": { + "w": 0.9868703484535217, + "x": -0.010178769007325172, + "y": -0.15702557563781738, + "z": 0.03642075136303902 + }, + "script": "file:///C:/Users/wayne/development/tryAgainBoxEntityScript.js", + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-08-29T23:01:55Z", + "dimensions": { + "blue": 0.26190000772476196, + "green": 0.5595999956130981, + "red": 0.5318999886512756, + "x": 0.5318999886512756, + "y": 0.5595999956130981, + "z": 0.26190000772476196 + }, + "id": "{8c9287c8-3826-4f2e-944e-05d8d4715fb8}", + "lastEdited": 1535584095676575, + "lastEditedBy": "{f5457c14-32ca-45c2-9dde-4f5b30b3be1b}", + "name": "Back Box", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.9590959548950195, + "green": 1.738053798675537, + "red": 5.0181121826171875, + "x": 5.0181121826171875, + "y": 1.738053798675537, + "z": 3.9590959548950195 + }, + "queryAACube": { + "scale": 0.8152676820755005, + "x": 4.610478401184082, + "y": 1.3304200172424316, + "z": 3.551462173461914 + }, + "rotation": { + "w": 0.9366722106933594, + "x": 0, + "y": -0.3502073884010315, + "z": 0 + }, + "script": "file:///C:/Users/wayne/development/backBoxEntityScript.js", + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":true}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 14.40000057220459, + "green": 14.40000057220459, + "red": 14.40000057220459, + "x": 14.40000057220459, + "y": 14.40000057220459, + "z": 14.40000057220459 + }, + "id": "{8e6bd656-d764-4724-a89d-7b4ff96abf8a}", + "lastEdited": 1535583477860044, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 2.3440732955932617, + "green": 1.7684326171875, + "red": 1.8812973499298096, + "x": 1.8812973499298096, + "y": 1.7684326171875, + "z": 2.3440732955932617 + }, + "queryAACube": { + "scale": 24.9415340423584, + "x": -10.589469909667969, + "y": -10.7023344039917, + "z": -10.126693725585938 + }, + "rotation": { + "w": 0.8697794675827026, + "x": -1.52587890625e-05, + "y": 0.4933699369430542, + "z": -4.57763671875e-05 + }, + "shapeType": "box", + "skyboxMode": "enabled", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 0.25, + "clientOnly": false, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255, + "x": 255, + "y": 255, + "z": 255 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 13.24000072479248, + "green": 13.24000072479248, + "red": 13.24000072479248, + "x": 13.24000072479248, + "y": 13.24000072479248, + "z": 13.24000072479248 + }, + "emitAcceleration": { + "blue": 0, + "green": 0.10000000149011612, + "red": 0, + "x": 0, + "y": 0.10000000149011612, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "emitRate": 6, + "emitSpeed": 0, + "id": "{b5ba3aa4-2eb9-4cfd-aec7-43635511c6b7}", + "lastEdited": 1535583477861791, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "lifespan": 10, + "maxParticles": 10, + "name": "Stars", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.07000000029802322, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 1.3712034225463867, + "green": 0.5220947265625, + "red": 2.6281180381774902, + "x": 2.6281180381774902, + "y": 0.5220947265625, + "z": 1.3712034225463867 + }, + "queryAACube": { + "scale": 22.932353973388672, + "x": -8.838058471679688, + "y": -10.944082260131836, + "z": -10.09497356414795 + }, + "radiusFinish": 0, + "radiusStart": 0, + "rotation": { + "w": 0.9852597713470459, + "x": -1.52587890625e-05, + "y": -0.17106890678405762, + "z": -7.62939453125e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{12a26c55-c411-4c4c-a124-cac9c80f9532}", + "lastEdited": 1535583681180522, + "lastEditedBy": "{f5457c14-32ca-45c2-9dde-4f5b30b3be1b}", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.9063844680786133, + "green": 1.15850830078125, + "red": 0.16632509231567383, + "x": 0.16632509231567383, + "y": 1.15850830078125, + "z": 1.9063844680786133 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -5.674418926239014, + "y": -4.6822357177734375, + "z": -3.934359550476074 + }, + "rotation": { + "w": 0.9666743278503418, + "x": -4.57763671875e-05, + "y": -0.2560006380081177, + "z": 1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{e8fd2c79-1303-49e4-8e4a-823272d6558c}", + "lastEdited": 1535583477862445, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.3116319179534912, + "green": 0.0772705078125, + "red": 2.705613613128662, + "x": 2.705613613128662, + "y": 0.0772705078125, + "z": 1.3116319179534912 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 2.5324084758758545, + "y": -0.09593456983566284, + "z": 1.1384267807006836 + }, + "rotation": { + "w": -0.38777750730514526, + "x": -0.37337303161621094, + "y": -0.8409399390220642, + "z": 0.055222392082214355 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 9.030570983886719, + "green": 0.0719478651881218, + "red": 9.030570983886719, + "x": 9.030570983886719, + "y": 0.0719478651881218, + "z": 9.030570983886719 + }, + "id": "{85b4551d-c05d-4038-b188-30ad5db9c23a}", + "lastEdited": 1535583477861514, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/floor.fbx", + "name": "Floor", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 4.2324604988098145, + "green": 0.17547607421875, + "red": 4.802988529205322, + "x": 4.802988529205322, + "y": 0.17547607421875, + "z": 4.2324604988098145 + }, + "queryAACube": { + "scale": 12.771358489990234, + "x": -1.582690715789795, + "y": -6.210203170776367, + "z": -2.1532187461853027 + }, + "rotation": { + "w": 0.8648051023483276, + "x": -1.52587890625e-05, + "y": 0.5020675659179688, + "z": -4.57763671875e-05 + }, + "shapeType": "simple-hull", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 0.06014331430196762, + "green": 2.582186460494995, + "red": 2.582186698913574, + "x": 2.582186698913574, + "y": 2.582186460494995, + "z": 0.06014331430196762 + }, + "id": "{8e4d8047-c2e2-4b2c-bfa2-c732d419d59a}", + "lastEdited": 1535583477860645, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/oopsDialog.fbx", + "name": "Oops Dialog", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.45927095413208, + "green": 1.6763916015625, + "red": 0, + "x": 0, + "y": 1.6763916015625, + "z": 1.45927095413208 + }, + "queryAACube": { + "scale": 3.6522583961486816, + "x": -1.8261291980743408, + "y": -0.14973759651184082, + "z": -0.36685824394226074 + }, + "rotation": { + "w": 0.8684672117233276, + "x": -4.57763671875e-05, + "y": 0.4957197904586792, + "z": -7.62939453125e-05 + }, + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{4fc1436d-00e7-43ec-83e1-f70cff544885}", + "lastEdited": 1535583477862827, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.8216662406921387, + "green": 0.15618896484375, + "red": 1.2409718036651611, + "x": 1.2409718036651611, + "y": 0.15618896484375, + "z": 3.8216662406921387 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 1.0677666664123535, + "y": -0.017016112804412842, + "z": 3.648461103439331 + }, + "rotation": { + "w": 0.6747081279754639, + "x": -0.06532388925552368, + "y": -0.6342412233352661, + "z": 0.37175559997558594 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{81a7c620-88ea-4056-b148-449d963c7eb5}", + "lastEdited": 1535583477860260, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 0, + "green": 1.1583251953125, + "red": 4.971565246582031, + "x": 4.971565246582031, + "y": 1.1583251953125, + "z": 0 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -0.8691787719726562, + "y": -4.6824188232421875, + "z": -5.8407440185546875 + }, + "rotation": { + "w": 0.8637980222702026, + "x": -4.57763671875e-05, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 2.1097896099090576, + "green": 0.04847164824604988, + "red": 1.458284616470337, + "x": 1.458284616470337, + "y": 0.04847164824604988, + "z": 2.1097896099090576 + }, + "id": "{09436993-6042-49ff-bfec-0fd28f792251}", + "lastEdited": 1535583477863518, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal1.fbx", + "name": "Try Again", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.946338653564453, + "green": 0.2467041015625, + "red": 1.6013128757476807, + "x": 1.6013128757476807, + "y": 0.2467041015625, + "z": 3.946338653564453 + }, + "queryAACube": { + "scale": 2.5651814937591553, + "x": 0.318722128868103, + "y": -1.0358866453170776, + "z": 2.663747787475586 + }, + "rotation": { + "w": 0.8220492601394653, + "x": -1.52587890625e-05, + "y": 0.5693598985671997, + "z": -0.0001068115234375 + }, + "script": "file:///C:/Users/wayne/development/tryAgainEntityScript.js", + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{86188ae0-6b36-4753-a5c0-0ba31b8ca89a}", + "lastEdited": 1535583477861108, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.3116319179534912, + "green": 0, + "red": 2.705613613128662, + "x": 2.705613613128662, + "y": 0, + "z": 1.3116319179534912 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 2.5324084758758545, + "y": -0.17320507764816284, + "z": 1.1384267807006836 + }, + "rotation": { + "w": 0.9127794504165649, + "x": 0.2575265169143677, + "y": 0.15553522109985352, + "z": 0.2761729955673218 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{ffa954a9-43e4-47b1-82c9-b921ed3414b0}", + "lastEdited": 1535583477861316, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.8216662406921387, + "green": 0.0772705078125, + "red": 1.2409718036651611, + "x": 1.2409718036651611, + "y": 0.0772705078125, + "z": 3.8216662406921387 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 1.0677666664123535, + "y": -0.09593456983566284, + "z": 3.648461103439331 + }, + "rotation": { + "w": 0.926024317741394, + "x": 0.20308232307434082, + "y": -0.010269343852996826, + "z": 0.3179827928543091 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{e86df231-a55a-4ca7-ae68-d3271a543d10}", + "lastEdited": 1535583477860886, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.8216662406921387, + "green": 0, + "red": 1.2409718036651611, + "x": 1.2409718036651611, + "y": 0, + "z": 3.8216662406921387 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 1.0677666664123535, + "y": -0.17320507764816284, + "z": 3.648461103439331 + }, + "rotation": { + "w": 0.6444342136383057, + "x": -0.08220034837722778, + "y": -0.6649118661880493, + "z": 0.3684900999069214 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{252ffb06-db92-4732-9315-7ee293c24ab8}", + "lastEdited": 1535583477862267, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.3116319179534912, + "green": 0.15618896484375, + "red": 2.705613613128662, + "x": 2.705613613128662, + "y": 0.15618896484375, + "z": 1.3116319179534912 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 2.5324084758758545, + "y": -0.017016112804412842, + "z": 1.1384267807006836 + }, + "rotation": { + "w": 0.46953535079956055, + "x": -0.16719311475753784, + "y": -0.7982757091522217, + "z": 0.3380941152572632 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{1c93807b-311d-48cd-af5e-b477d33b2811}", + "lastEdited": 1535583477860455, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 5.268576622009277, + "green": 1.1588134765625, + "red": 6.100250244140625, + "x": 6.100250244140625, + "y": 1.1588134765625, + "z": 5.268576622009277 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": 0.2595062255859375, + "y": -4.6819305419921875, + "z": -0.5721673965454102 + }, + "rotation": { + "w": 0.9662165641784668, + "x": -4.57763671875e-05, + "y": -0.2576791048049927, + "z": 1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 1, + "clientOnly": false, + "color": { + "blue": 255, + "green": 205, + "red": 3 + }, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 204, + "red": 0, + "x": 0, + "y": 204, + "z": 255 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 2.5, + "green": 2.5, + "red": 2.5, + "x": 2.5, + "y": 2.5, + "z": 2.5 + }, + "emitAcceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 0.9993909597396851, + "x": 0.034897372126579285, + "y": -1.525880907138344e-05, + "z": -1.525880907138344e-05 + }, + "emitRate": 2, + "emitSpeed": 0, + "emitterShouldTrail": true, + "id": "{4cb534d1-5494-4aaa-aa28-ff5cee6e0b8f}", + "lastEdited": 1535583477862081, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "lifespan": 10, + "maxParticles": 40, + "name": "Rays", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.75, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 3.814434051513672, + "green": 1.44122314453125, + "red": 1.2319090366363525, + "x": 1.2319090366363525, + "y": 1.44122314453125, + "z": 3.814434051513672 + }, + "queryAACube": { + "scale": 4.330127239227295, + "x": -0.9331545829772949, + "y": -0.7238404750823975, + "z": 1.6493704319000244 + }, + "radiusFinish": 0.10000000149011612, + "radiusStart": 0, + "rotation": { + "w": 0.9594720602035522, + "x": -1.52587890625e-05, + "y": 0.28178834915161133, + "z": -4.57763671875e-05 + }, + "script": "file:///C:/Users/wayne/development/raysTryAgainEntityScript.js", + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{a58a8ffe-17ee-4154-b8bb-54e2f0ca9a9d}", + "lastEdited": 1535583477862953, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 6.1806135177612305, + "green": 1.1588134765625, + "red": 1.4755167961120605, + "x": 1.4755167961120605, + "y": 1.1588134765625, + "z": 6.1806135177612305 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -4.365227222442627, + "y": -4.6819305419921875, + "z": 0.33986949920654297 + }, + "rotation": { + "w": 0.8637980222702026, + "x": -4.57763671875e-05, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 0.25, + "clientOnly": false, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255, + "x": 255, + "y": 255, + "z": 255 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 13.24000072479248, + "green": 13.24000072479248, + "red": 13.24000072479248, + "x": 13.24000072479248, + "y": 13.24000072479248, + "z": 13.24000072479248 + }, + "emitAcceleration": { + "blue": 0, + "green": 0.10000000149011612, + "red": 0, + "x": 0, + "y": 0.10000000149011612, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "emitRate": 6, + "emitSpeed": 0, + "emitterShouldTrail": true, + "id": "{b6c56e83-f098-422d-a0e8-fd1497c62fbe}", + "lastEdited": 1535583477863251, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "lifespan": 10, + "maxParticles": 10, + "name": "Stars", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.07000000029802322, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 3.78922963142395, + "green": 0.5220947265625, + "red": 1.1928560733795166, + "x": 1.1928560733795166, + "y": 0.5220947265625, + "z": 3.78922963142395 + }, + "queryAACube": { + "scale": 22.932353973388672, + "x": -10.273321151733398, + "y": -10.944082260131836, + "z": -7.676947593688965 + }, + "radiusFinish": 0, + "radiusStart": 0, + "rotation": { + "w": 0.996429443359375, + "x": -1.52587890625e-05, + "y": -0.08442819118499756, + "z": -4.57763671875e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 1, + "clientOnly": false, + "color": { + "blue": 211, + "green": 227, + "red": 104 + }, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 211, + "green": 227, + "red": 104, + "x": 104, + "y": 227, + "z": 211 + }, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 2.5, + "green": 2.5, + "red": 2.5, + "x": 2.5, + "y": 2.5, + "z": 2.5 + }, + "emitAcceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 0.9993909597396851, + "x": 0.034897372126579285, + "y": -1.525880907138344e-05, + "z": -1.525880907138344e-05 + }, + "emitRate": 2, + "emitSpeed": 0, + "id": "{f87fab56-c8d8-4ff8-abf0-35a76dfccf2b}", + "lastEdited": 1535583477862685, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "lifespan": 10, + "maxParticles": 40, + "name": "Rays", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.75, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 1.3553659915924072, + "green": 1.44122314453125, + "red": 2.572803497314453, + "x": 2.572803497314453, + "y": 1.44122314453125, + "z": 1.3553659915924072 + }, + "queryAACube": { + "scale": 4.330127239227295, + "x": 0.40773987770080566, + "y": -0.7238404750823975, + "z": -0.8096976280212402 + }, + "radiusFinish": 0.10000000149011612, + "radiusStart": 0, + "rotation": { + "w": 0.9803768396377563, + "x": -1.52587890625e-05, + "y": 0.19707024097442627, + "z": -7.62939453125e-05 + }, + "script": "file:///C:/Users/wayne/development/raysBackEntityScript.js", + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "created": "2018-08-29T22:57:33Z", + "dimensions": { + "blue": 2.1097896099090576, + "green": 0.04847164824604988, + "red": 1.458284616470337, + "x": 1.458284616470337, + "y": 0.04847164824604988, + "z": 2.1097896099090576 + }, + "id": "{1af53cfc-0cce-467a-96e0-a937f2651ce2}", + "lastEdited": 1535583477863389, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal2.fbx", + "name": "Back", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.5835940837860107, + "green": 0.2467041015625, + "red": 3.0345542430877686, + "x": 3.0345542430877686, + "y": 0.2467041015625, + "z": 1.5835940837860107 + }, + "queryAACube": { + "scale": 2.5651814937591553, + "x": 1.751963496208191, + "y": -1.0358866453170776, + "z": 0.3010033369064331 + }, + "rotation": { + "w": 0.9084458351135254, + "x": -1.52587890625e-05, + "y": 0.4179598093032837, + "z": -0.0001068115234375 + }, + "script": "file:///C:/Users/wayne/development/tryAgainEntityScript.js", + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + } + ], + "Id": "{351e561a-ee5e-4e8b-87ab-d28677d3b374}", + "Version": 93 +} From a5243fbd7e5048c0a17ed28d1b712e5a713298c2 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 29 Aug 2018 16:12:50 -0700 Subject: [PATCH 141/744] commenting out notification for script --- scripts/system/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 0778e2a44b..13d500b909 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -624,7 +624,7 @@ Controller.keyReleaseEvent.connect(keyReleaseEvent); Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); - Window.domainConnectionRefused.connect(onDomainConnectionRefused); + //Window.domainConnectionRefused.connect(onDomainConnectionRefused); Window.stillSnapshotTaken.connect(onSnapshotTaken); Window.snapshot360Taken.connect(onSnapshotTaken); Window.processingGifStarted.connect(processingGif); From 686f9fb18a623d6ce93aa988c7ee3f5f27e1c0cb Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 29 Aug 2018 16:12:58 -0700 Subject: [PATCH 142/744] Move load state flag from CollisionPickResult to CollisionRegion and make it a boolean --- interface/src/raypick/CollisionPick.cpp | 48 +++++++++++-------- interface/src/raypick/CollisionPick.h | 17 ++----- .../src/raypick/PickScriptingInterface.h | 3 -- libraries/shared/src/RegisteredMetaTypes.h | 9 +++- 4 files changed, 41 insertions(+), 36 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 091ceb0794..b2137fc80c 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -32,9 +32,6 @@ PickResultPointer CollisionPickResult::compareAndProcessNewResult(const PickResu } intersects = entityIntersections.size() || avatarIntersections.size(); - if (newCollisionResult->loadState == LOAD_STATE_NOT_LOADED || loadState == LOAD_STATE_UNKNOWN) { - loadState = (LoadState)newCollisionResult->loadState; - } return std::make_shared(*this); } @@ -80,24 +77,29 @@ QVariantMap CollisionPickResult::toVariantMap() const { } variantMap["intersectingObjects"] = qIntersectingObjects; - variantMap["loaded"] = (loadState == LOAD_STATE_LOADED); variantMap["collisionRegion"] = pickVariant; return variantMap; } +bool CollisionPick::isLoaded() const { + return !_mathPick.shouldComputeShapeInfo() || (_cachedResource && _cachedResource->isLoaded()); +} + bool CollisionPick::getShapeInfoReady() { if (_mathPick.shouldComputeShapeInfo()) { if (_cachedResource && _cachedResource->isLoaded()) { computeShapeInfo(_mathPick, *_mathPick.shapeInfo, _cachedResource); - return true; + _mathPick.loaded = true; + } else { + _mathPick.loaded = false; } - - return false; } else { computeShapeInfoDimensionsOnly(_mathPick, *_mathPick.shapeInfo, _cachedResource); - return true; + _mathPick.loaded = true; } + + return _mathPick.loaded; } void CollisionPick::computeShapeInfoDimensionsOnly(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { @@ -349,15 +351,19 @@ CollisionPick::CollisionPick(const PickFilter& filter, float maxDistance, bool e if (collisionRegion.shouldComputeShapeInfo()) { _cachedResource = DependencyManager::get()->getCollisionGeometryResource(collisionRegion.modelURL); } + _mathPick.loaded = isLoaded(); } CollisionRegion CollisionPick::getMathematicalPick() const { + CollisionRegion mathPick = _mathPick; + if (!mathPick.loaded) { + mathPick.loaded = isLoaded(); + } if (!parentTransform) { - return _mathPick; + return mathPick; } else { - CollisionRegion transformedMathPick = _mathPick; - transformedMathPick.transform = parentTransform->getTransform().worldTransform(_mathPick.transform); - return transformedMathPick; + mathPick.transform = parentTransform->getTransform().worldTransform(mathPick.transform); + return mathPick; } } @@ -385,33 +391,35 @@ void CollisionPick::filterIntersections(std::vector& intersec } PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pick) { - if (!getShapeInfoReady()) { + if (!pick.loaded) { // Cannot compute result - return std::make_shared(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); + return std::make_shared(pick.toVariantMap(), std::vector(), std::vector()); } + getShapeInfoReady(); auto entityIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_ENTITIES, *pick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold); filterIntersections(entityIntersections); - return std::make_shared(pick, CollisionPickResult::LOAD_STATE_LOADED, entityIntersections, std::vector()); + return std::make_shared(pick, entityIntersections, std::vector()); } PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) { - return std::make_shared(pick, getShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); + return std::make_shared(pick, std::vector(), std::vector()); } PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) { - if (!getShapeInfoReady()) { + if (!pick.loaded) { // Cannot compute result - return std::make_shared(pick, CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); + return std::make_shared(pick, std::vector(), std::vector()); } + getShapeInfoReady(); auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *pick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold); filterIntersections(avatarIntersections); - return std::make_shared(pick, CollisionPickResult::LOAD_STATE_LOADED, std::vector(), avatarIntersections); + return std::make_shared(pick, std::vector(), avatarIntersections); } PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) { - return std::make_shared(pick.toVariantMap(), getShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); + return std::make_shared(pick.toVariantMap(), std::vector(), std::vector()); } Transform CollisionPick::getResultTransform() const { diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index f46f0742f5..a1636e4b47 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -16,18 +16,11 @@ class CollisionPickResult : public PickResult { public: - enum LoadState { - LOAD_STATE_UNKNOWN, - LOAD_STATE_NOT_LOADED, - LOAD_STATE_LOADED - }; - CollisionPickResult() {} CollisionPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} - CollisionPickResult(const CollisionRegion& searchRegion, LoadState loadState, const std::vector& entityIntersections, const std::vector& avatarIntersections) : + CollisionPickResult(const CollisionRegion& searchRegion, const std::vector& entityIntersections, const std::vector& avatarIntersections) : PickResult(searchRegion.toVariantMap()), - loadState(loadState), intersects(entityIntersections.size() || avatarIntersections.size()), entityIntersections(entityIntersections), avatarIntersections(avatarIntersections) @@ -38,10 +31,8 @@ public: avatarIntersections = collisionPickResult.avatarIntersections; entityIntersections = collisionPickResult.entityIntersections; intersects = collisionPickResult.intersects; - loadState = collisionPickResult.loadState; } - LoadState loadState { LOAD_STATE_UNKNOWN }; bool intersects { false }; std::vector entityIntersections; std::vector avatarIntersections; @@ -61,7 +52,7 @@ public: CollisionRegion getMathematicalPick() const override; PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { - return std::make_shared(pickVariant, CollisionPickResult::LOAD_STATE_UNKNOWN, std::vector(), std::vector()); + return std::make_shared(pickVariant, std::vector(), std::vector()); } PickResultPointer getEntityIntersection(const CollisionRegion& pick) override; PickResultPointer getOverlayIntersection(const CollisionRegion& pick) override; @@ -69,7 +60,9 @@ public: PickResultPointer getHUDIntersection(const CollisionRegion& pick) override; Transform getResultTransform() const override; protected: - // Returns true if pick.shapeInfo is valid. Otherwise, attempts to get the shapeInfo ready for use. + // Returns true if the resource for _mathPick.shapeInfo is loaded or if a resource is not needed. + bool isLoaded() const; + // Returns true if _mathPick.shapeInfo is valid. Otherwise, attempts to get the _mathPick ready for use. bool getShapeInfoReady(); void computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); void computeShapeInfoDimensionsOnly(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 717436a13b..4d99309618 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -152,9 +152,6 @@ public: * @property {CollisionRegion} collisionRegion The CollisionRegion that was used. Valid even if there was no intersection. */ - // TODO: Add this to the CollisionPickResult jsdoc once model collision picks are working - //* @property {boolean} loaded If the CollisionRegion was successfully loaded (may be false if a model was used) - /**jsdoc * Information about the Collision Pick's intersection with an object * diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 8fefc37741..8fb1301484 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -253,6 +253,8 @@ public: } }; +// TODO: Add "loaded" to CollisionRegion jsdoc once model collision picks are supported. + /**jsdoc * A CollisionRegion defines a volume for checking collisions in the physics simulation. @@ -270,6 +272,7 @@ public: CollisionRegion() { } CollisionRegion(const CollisionRegion& collisionRegion) : + loaded(collisionRegion.loaded), modelURL(collisionRegion.modelURL), shapeInfo(std::make_shared()), transform(collisionRegion.transform), @@ -279,6 +282,7 @@ public: } CollisionRegion(const QVariantMap& pickVariant) { + // "loaded" is not deserialized here because there is no way to know if the shape is actually loaded if (pickVariant["shape"].isValid()) { auto shape = pickVariant["shape"].toMap(); if (!shape.empty()) { @@ -322,6 +326,7 @@ public: shape["dimensions"] = vec3toVariant(transform.getScale()); collisionRegion["shape"] = shape; + collisionRegion["loaded"] = loaded; collisionRegion["threshold"] = threshold; @@ -339,7 +344,8 @@ public: } bool operator==(const CollisionRegion& other) const { - return threshold == other.threshold && + return loaded == other.loaded && + threshold == other.threshold && glm::all(glm::equal(transform.getTranslation(), other.transform.getTranslation())) && glm::all(glm::equal(transform.getRotation(), other.transform.getRotation())) && glm::all(glm::equal(transform.getScale(), other.transform.getScale())) && @@ -359,6 +365,7 @@ public: } // We can't load the model here because it would create a circular dependency, so we delegate that responsibility to the owning CollisionPick + bool loaded = false; QUrl modelURL; // We can't compute the shapeInfo here without loading the model first, so we delegate that responsibility to the owning CollisionPick From 0926968e2bd260ce54a8bb0360632c50dd924921 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 29 Aug 2018 16:17:53 -0700 Subject: [PATCH 143/744] Do not have depth as a parameter in getTransform(..) when called in NestableTransformNode --- libraries/shared/src/NestableTransformNode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/NestableTransformNode.cpp b/libraries/shared/src/NestableTransformNode.cpp index 7fb7187aee..17456d69ce 100644 --- a/libraries/shared/src/NestableTransformNode.cpp +++ b/libraries/shared/src/NestableTransformNode.cpp @@ -21,7 +21,7 @@ Transform NestableTransformNode::getTransform() { } bool success; - Transform jointWorldTransform = nestable->getTransform(_jointIndex, success, 30); + Transform jointWorldTransform = nestable->getTransform(_jointIndex, success); if (success) { return jointWorldTransform; From 1fe909f9ad32a01c95dfa15766e8bbcd1cd8acd1 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 29 Aug 2018 16:33:51 -0700 Subject: [PATCH 144/744] Make CollisionPick::getResultTransform() always return a valid transform --- interface/src/raypick/CollisionPick.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index b2137fc80c..b163f23df6 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -423,11 +423,6 @@ PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) } Transform CollisionPick::getResultTransform() const { - PickResultPointer result = getPrevPickResult(); - if (!result) { - return Transform(); - } - Transform transform; transform.setTranslation(getMathematicalPick().transform.getTranslation()); return transform; From eb29c9c4a298bd13ec87832800edb1a24597e748 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 29 Aug 2018 16:53:22 -0700 Subject: [PATCH 145/744] updating with default landing location --- interface/resources/serverless/redirect.json | 130 ++++++++++--------- interface/src/Application.cpp | 1 - 2 files changed, 67 insertions(+), 64 deletions(-) diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index 9e3b29cc26..71dfae4212 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -23,9 +23,9 @@ "blue": 4.54052734375, "green": 1.722428798675537, "red": 4.593362331390381, - "x": 4.593362331390381, - "y": 1.722428798675537, - "z": 4.54052734375 + "x": 0.5933623313903809, + "y": -7.777571201324463, + "z": 0.54052734375 }, "queryAACube": { "scale": 0.8152676820755005, @@ -65,9 +65,9 @@ "blue": 3.9590959548950195, "green": 1.738053798675537, "red": 5.0181121826171875, - "x": 5.0181121826171875, - "y": 1.738053798675537, - "z": 3.9590959548950195 + "x": 1.0181121826171875, + "y": -7.761946201324463, + "z": -0.04090404510498047 }, "queryAACube": { "scale": 0.8152676820755005, @@ -106,9 +106,9 @@ "blue": 2.3440732955932617, "green": 1.7684326171875, "red": 1.8812973499298096, - "x": 1.8812973499298096, - "y": 1.7684326171875, - "z": 2.3440732955932617 + "x": -2.1187026500701904, + "y": -7.7315673828125, + "z": -1.6559267044067383 }, "queryAACube": { "scale": 24.9415340423584, @@ -194,9 +194,9 @@ "blue": 1.3712034225463867, "green": 0.5220947265625, "red": 2.6281180381774902, - "x": 2.6281180381774902, - "y": 0.5220947265625, - "z": 1.3712034225463867 + "x": -1.3718819618225098, + "y": -8.9779052734375, + "z": -2.6287965774536133 }, "queryAACube": { "scale": 22.932353973388672, @@ -243,9 +243,9 @@ "blue": 1.9063844680786133, "green": 1.15850830078125, "red": 0.16632509231567383, - "x": 0.16632509231567383, - "y": 1.15850830078125, - "z": 1.9063844680786133 + "x": -3.833674907684326, + "y": -8.34149169921875, + "z": -2.0936155319213867 }, "queryAACube": { "scale": 11.681488037109375, @@ -290,9 +290,9 @@ "blue": 1.3116319179534912, "green": 0.0772705078125, "red": 2.705613613128662, - "x": 2.705613613128662, - "y": 0.0772705078125, - "z": 1.3116319179534912 + "x": -1.294386386871338, + "y": -9.4227294921875, + "z": -2.688368082046509 }, "queryAACube": { "scale": 0.3464101552963257, @@ -334,9 +334,9 @@ "blue": 4.2324604988098145, "green": 0.17547607421875, "red": 4.802988529205322, - "x": 4.802988529205322, - "y": 0.17547607421875, - "z": 4.2324604988098145 + "x": 0.8029885292053223, + "y": -9.32452392578125, + "z": 0.23246049880981445 }, "queryAACube": { "scale": 12.771358489990234, @@ -375,9 +375,9 @@ "blue": 1.45927095413208, "green": 1.6763916015625, "red": 0, - "x": 0, - "y": 1.6763916015625, - "z": 1.45927095413208 + "x": -4, + "y": -7.8236083984375, + "z": -2.54072904586792 }, "queryAACube": { "scale": 3.6522583961486816, @@ -420,9 +420,9 @@ "blue": 3.8216662406921387, "green": 0.15618896484375, "red": 1.2409718036651611, - "x": 1.2409718036651611, - "y": 0.15618896484375, - "z": 3.8216662406921387 + "x": -2.759028196334839, + "y": -9.34381103515625, + "z": -0.17833375930786133 }, "queryAACube": { "scale": 0.3464101552963257, @@ -467,9 +467,9 @@ "blue": 0, "green": 1.1583251953125, "red": 4.971565246582031, - "x": 4.971565246582031, - "y": 1.1583251953125, - "z": 0 + "x": 0.9715652465820312, + "y": -8.3416748046875, + "z": -4 }, "queryAACube": { "scale": 11.681488037109375, @@ -509,9 +509,9 @@ "blue": 3.946338653564453, "green": 0.2467041015625, "red": 1.6013128757476807, - "x": 1.6013128757476807, - "y": 0.2467041015625, - "z": 3.946338653564453 + "x": -2.3986871242523193, + "y": -9.2532958984375, + "z": -0.053661346435546875 }, "queryAACube": { "scale": 2.5651814937591553, @@ -556,9 +556,9 @@ "blue": 1.3116319179534912, "green": 0, "red": 2.705613613128662, - "x": 2.705613613128662, - "y": 0, - "z": 1.3116319179534912 + "x": -1.294386386871338, + "y": -9.5, + "z": -2.688368082046509 }, "queryAACube": { "scale": 0.3464101552963257, @@ -605,9 +605,9 @@ "blue": 3.8216662406921387, "green": 0.0772705078125, "red": 1.2409718036651611, - "x": 1.2409718036651611, - "y": 0.0772705078125, - "z": 3.8216662406921387 + "x": -2.759028196334839, + "y": -9.4227294921875, + "z": -0.17833375930786133 }, "queryAACube": { "scale": 0.3464101552963257, @@ -654,9 +654,9 @@ "blue": 3.8216662406921387, "green": 0, "red": 1.2409718036651611, - "x": 1.2409718036651611, - "y": 0, - "z": 3.8216662406921387 + "x": -2.759028196334839, + "y": -9.5, + "z": -0.17833375930786133 }, "queryAACube": { "scale": 0.3464101552963257, @@ -703,9 +703,9 @@ "blue": 1.3116319179534912, "green": 0.15618896484375, "red": 2.705613613128662, - "x": 2.705613613128662, - "y": 0.15618896484375, - "z": 1.3116319179534912 + "x": -1.294386386871338, + "y": -9.34381103515625, + "z": -2.688368082046509 }, "queryAACube": { "scale": 0.3464101552963257, @@ -750,9 +750,9 @@ "blue": 5.268576622009277, "green": 1.1588134765625, "red": 6.100250244140625, - "x": 6.100250244140625, - "y": 1.1588134765625, - "z": 5.268576622009277 + "x": 2.100250244140625, + "y": -8.3411865234375, + "z": 1.2685766220092773 }, "queryAACube": { "scale": 11.681488037109375, @@ -844,9 +844,9 @@ "blue": 3.814434051513672, "green": 1.44122314453125, "red": 1.2319090366363525, - "x": 1.2319090366363525, - "y": 1.44122314453125, - "z": 3.814434051513672 + "x": -2.7680909633636475, + "y": -8.05877685546875, + "z": -0.18556594848632812 }, "queryAACube": { "scale": 4.330127239227295, @@ -894,9 +894,9 @@ "blue": 6.1806135177612305, "green": 1.1588134765625, "red": 1.4755167961120605, - "x": 1.4755167961120605, - "y": 1.1588134765625, - "z": 6.1806135177612305 + "x": -2.5244832038879395, + "y": -8.3411865234375, + "z": 2.1806135177612305 }, "queryAACube": { "scale": 11.681488037109375, @@ -983,9 +983,9 @@ "blue": 3.78922963142395, "green": 0.5220947265625, "red": 1.1928560733795166, - "x": 1.1928560733795166, - "y": 0.5220947265625, - "z": 3.78922963142395 + "x": -2.8071439266204834, + "y": -8.9779052734375, + "z": -0.2107703685760498 }, "queryAACube": { "scale": 22.932353973388672, @@ -1080,9 +1080,9 @@ "blue": 1.3553659915924072, "green": 1.44122314453125, "red": 2.572803497314453, - "x": 2.572803497314453, - "y": 1.44122314453125, - "z": 1.3553659915924072 + "x": -1.4271965026855469, + "y": -8.05877685546875, + "z": -2.6446340084075928 }, "queryAACube": { "scale": 4.330127239227295, @@ -1127,9 +1127,9 @@ "blue": 1.5835940837860107, "green": 0.2467041015625, "red": 3.0345542430877686, - "x": 3.0345542430877686, - "y": 0.2467041015625, - "z": 1.5835940837860107 + "x": -0.9654457569122314, + "y": -9.2532958984375, + "z": -2.4164059162139893 }, "queryAACube": { "scale": 2.5651814937591553, @@ -1149,6 +1149,10 @@ "userData": "{\"grabbableKey\":{\"grabbable\":false}}" } ], + "Paths": + { + "/": "/1.46,-9,0.7/0,0.487,0,0.86663" + }, "Id": "{351e561a-ee5e-4e8b-87ab-d28677d3b374}", "Version": 93 } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6e15cc0dd8..348d1d5690 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2369,7 +2369,6 @@ void Application::domainConnectionRedirect(const QString& reasonMessage, int rea addressManager->handleLookupString(REDIRECT_HIFI_ADDRESS); getMyAvatar()->setWorldVelocity(glm::vec3(0.0f)); // in (w, x, y, z) component-structure for the constructor - getMyAvatar()->setWorldOrientation(glm::quat(0.8775935173034668f, 0.0f, 0.4794054925441742f, 0.0f)); break; } default: From 4844f06e5e172e6b2e1b61f694145d5f5dbe649b Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Wed, 29 Aug 2018 17:33:39 -0700 Subject: [PATCH 146/744] brain freeze --- interface/src/LODManager.cpp | 4 +--- scripts/developer/utilities/lib/plotperf/PlotPerf.qml | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index ce674f7295..df371f3a59 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -105,9 +105,7 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { auto error = (targetFPS - currentFPS) / targetFPS; error = glm::clamp(error, -1.0f, 1.0f); - if (error <= 0.0f) { - // error = error * 2.0f; - } + auto integral = previous_integral + error * dt; glm::clamp(integral, -1.0f, 1.0f); diff --git a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml index 46056be5fd..27101a2867 100644 --- a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml +++ b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml @@ -186,10 +186,10 @@ Item { } function pixelFromVal(val, valScale) { - return lineHeight + (height - lineHeight) * (1 - (0.9) * (val - _displayMinValue) / (_displayMaxValue - _displayMinValue)); + return lineHeight + (height - lineHeight) * (1 - (0.99) * (val - _displayMinValue) / (_displayMaxValue - _displayMinValue)); } function valueFromPixel(pixY) { - return _displayMinValue + (((pixY - lineHeight) / (height - lineHeight) - 1) * (_displayMaxValue - _displayMinValue) / (-0.9)); + return _displayMinValue + (((pixY - lineHeight) / (height - lineHeight) - 1) * (_displayMaxValue - _displayMinValue) / (-0.99)); } function plotValueHistory(ctx, valHistory, color) { var widthStep= width / (valHistory.length - 1); From a186be014da18676776ae88865d3d45a9008d961 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 29 Aug 2018 17:35:28 -0700 Subject: [PATCH 147/744] Use std::chrono for usecTimestampNow() --- libraries/shared/src/SharedUtil.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index bb22a1e753..3bef7f7e18 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -132,6 +133,9 @@ static std::once_flag usecTimestampNowIsInitialized; static QElapsedTimer timestampTimer; quint64 usecTimestampNow(bool wantDebug) { + using namespace std::chrono; + return duration_cast(high_resolution_clock::now().time_since_epoch()).count(); +#if 0 std::call_once(usecTimestampNowIsInitialized, [&] { TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec timestampTimer.start(); @@ -203,6 +207,7 @@ quint64 usecTimestampNow(bool wantDebug) { } return now; +#endif } float secTimestampNow() { From 248c2621a43a6b9dc67e9d6002f7aa7f77e769e4 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 29 Aug 2018 17:57:54 -0700 Subject: [PATCH 148/744] fix blendshapes crash --- .../render-utils/src/CauterizedModel.cpp | 5 +- libraries/render-utils/src/Model.cpp | 87 +++++++++---------- libraries/render-utils/src/Model.h | 2 + 3 files changed, 45 insertions(+), 49 deletions(-) diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 3966d04b6d..259036f500 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -86,9 +86,8 @@ void CauterizedModel::createRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { - if (!fbxGeometry.meshes[i].blendshapes.empty() && !_blendedVertexBuffers[i]) { - _blendedVertexBuffers[i] = std::make_shared(); - _blendedVertexBuffers[i]->resize(fbxGeometry.meshes[i].vertices.size() * (sizeof(glm::vec3) + 2 * sizeof(NormalType))); + if (!fbxGeometry.meshes[i].blendshapes.empty()) { + initializeBlendshapes(fbxGeometry.meshes[i], i); } auto ptr = std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); _modelMeshRenderItems << std::static_pointer_cast(ptr); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 71250c6483..946275a312 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -302,52 +302,11 @@ bool Model::updateGeometry() { assert(_meshStates.empty()); const FBXGeometry& fbxGeometry = getFBXGeometry(); - int i = 0; foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; state.clusterDualQuaternions.resize(mesh.clusters.size()); state.clusterMatrices.resize(mesh.clusters.size()); _meshStates.push_back(state); - - if (!mesh.blendshapes.isEmpty()) { - if (!_blendedVertexBuffers[i]) { - _blendedVertexBuffers[i] = std::make_shared(); - } - const auto& buffer = _blendedVertexBuffers[i]; - QVector normalsAndTangents; - normalsAndTangents.resize(2 * mesh.normals.size()); - - // Interleave normals and tangents - // Parallel version for performance - tbb::parallel_for(tbb::blocked_range(0, mesh.normals.size()), [&](const tbb::blocked_range& range) { - auto normalsRange = std::make_pair(mesh.normals.begin() + range.begin(), mesh.normals.begin() + range.end()); - auto tangentsRange = std::make_pair(mesh.tangents.begin() + range.begin(), mesh.tangents.begin() + range.end()); - auto normalsAndTangentsIt = normalsAndTangents.begin() + 2 * range.begin(); - - for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; - normalIt != normalsRange.second; - ++normalIt, ++tangentIt) { -#if FBX_PACK_NORMALS - glm::uint32 finalNormal; - glm::uint32 finalTangent; - buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); -#else - const auto& finalNormal = *normalIt; - const auto& finalTangent = *tangentIt; -#endif - *normalsAndTangentsIt = finalNormal; - ++normalsAndTangentsIt; - *normalsAndTangentsIt = finalTangent; - ++normalsAndTangentsIt; - } - }); - const auto verticesSize = mesh.vertices.size() * sizeof(glm::vec3); - buffer->resize(mesh.vertices.size() * sizeof(glm::vec3) + normalsAndTangents.size() * sizeof(NormalType)); - buffer->setSubData(0, verticesSize, (const gpu::Byte*) mesh.vertices.constData()); - buffer->setSubData(verticesSize, 2 * mesh.normals.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); - mesh.normalsAndTangents = normalsAndTangents; - } - i++; } needFullUpdate = true; emit rigReady(); @@ -1514,10 +1473,10 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo assert(buffer); buffer->resize(mesh.vertices.size() * sizeof(glm::vec3) + mesh.normalsAndTangents.size() * sizeof(NormalType)); buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3)); - buffer->setSubData(verticesSize, 2 * mesh.normals.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType)); + buffer->setSubData(verticesSize, mesh.normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType)); index += vertexCount; - normalAndTangentIndex += 2 * mesh.normals.size(); + normalAndTangentIndex += mesh.normalsAndTangents.size(); } } @@ -1555,6 +1514,42 @@ const render::ItemIDs& Model::fetchRenderItemIDs() const { return _modelMeshRenderItemIDs; } +void Model::initializeBlendshapes(const FBXMesh& mesh, int index) { + QVector normalsAndTangents; + normalsAndTangents.resize(2 * mesh.normals.size()); + + // Interleave normals and tangents + // Parallel version for performance + tbb::parallel_for(tbb::blocked_range(0, mesh.normals.size()), [&](const tbb::blocked_range& range) { + auto normalsRange = std::make_pair(mesh.normals.begin() + range.begin(), mesh.normals.begin() + range.end()); + auto tangentsRange = std::make_pair(mesh.tangents.begin() + range.begin(), mesh.tangents.begin() + range.end()); + auto normalsAndTangentsIt = normalsAndTangents.begin() + 2 * range.begin(); + + for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; + normalIt != normalsRange.second; + ++normalIt, ++tangentIt) { +#if FBX_PACK_NORMALS + glm::uint32 finalNormal; + glm::uint32 finalTangent; + buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); +#else + const auto& finalNormal = *normalIt; + const auto& finalTangent = *tangentIt; +#endif + *normalsAndTangentsIt = finalNormal; + ++normalsAndTangentsIt; + *normalsAndTangentsIt = finalTangent; + ++normalsAndTangentsIt; + } + }); + const auto verticesSize = mesh.vertices.size() * sizeof(glm::vec3); + _blendedVertexBuffers[index] = std::make_shared(); + _blendedVertexBuffers[index]->resize(mesh.vertices.size() * sizeof(glm::vec3) + normalsAndTangents.size() * sizeof(NormalType)); + _blendedVertexBuffers[index]->setSubData(0, verticesSize, (const gpu::Byte*) mesh.vertices.constData()); + _blendedVertexBuffers[index]->setSubData(verticesSize, normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); + mesh.normalsAndTangents = normalsAndTangents; +} + void Model::createRenderItemSet() { assert(isLoaded()); const auto& meshes = _renderGeometry->getMeshes(); @@ -1583,7 +1578,7 @@ void Model::createRenderItemSet() { // Run through all of the meshes, and place them into their segregated, but unsorted buckets int shapeID = 0; uint32_t numMeshes = (uint32_t)meshes.size(); - auto fbxGeometry = getFBXGeometry(); + auto& fbxGeometry = getFBXGeometry(); for (uint32_t i = 0; i < numMeshes; i++) { const auto& mesh = meshes.at(i); if (!mesh) { @@ -1593,8 +1588,8 @@ void Model::createRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { - if (fbxGeometry.meshes[i].blendshapes.empty() && !_blendedVertexBuffers[i]) { - _blendedVertexBuffers[i] = std::make_shared(); + if (!fbxGeometry.meshes[i].blendshapes.empty()) { + initializeBlendshapes(fbxGeometry.meshes[i], i); } _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); auto material = getGeometry()->getShapeMaterial(shapeID); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 677be61261..eaca068c10 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -482,6 +482,8 @@ protected: bool shouldInvalidatePayloadShapeKey(int meshIndex); + void initializeBlendshapes(const FBXMesh& mesh, int index); + private: float _loadingPriority { 0.0f }; From 7b7f369c394edd715d2e143cce321324f4d5dfff Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 29 Aug 2018 18:25:10 -0700 Subject: [PATCH 149/744] Further reductions in shared pointers, getWorldPosition(). --- .../src/avatars/AvatarMixerClientData.cpp | 4 +++ .../src/avatars/AvatarMixerClientData.h | 1 + .../src/avatars/AvatarMixerSlave.cpp | 34 +++++++++---------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index e003fdd769..1dd4cc769b 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -218,6 +218,10 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node } void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) { + ignoreOther(self.data(), other.data()); +} + +void AvatarMixerClientData::ignoreOther(const Node* self, const Node* other) { if (!isRadiusIgnoring(other->getUUID())) { addToRadiusIgnoringSet(other->getUUID()); auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index a892455fe3..7d203bd771 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -95,6 +95,7 @@ public: void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); } void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other); void ignoreOther(SharedNodePointer self, SharedNodePointer other); + void ignoreOther(const Node* self, const Node* other); void readViewFrustumPacket(const QByteArray& message); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index f0c291b2c2..e772fa4d08 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -213,7 +213,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { } void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { - const Node* nodeRaw = node.data(); + const Node* destinationNode = node.data(); auto nodeList = DependencyManager::get(); @@ -224,7 +224,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.nodesBroadcastedTo++; - AvatarMixerClientData* nodeData = reinterpret_cast(nodeRaw->getLinkedData()); + AvatarMixerClientData* nodeData = reinterpret_cast(destinationNode->getLinkedData()); nodeData->resetInViewStats(); @@ -264,7 +264,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) bool PALIsOpen = nodeData->getRequestsDomainListData(); // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them - bool getsAnyIgnored = PALIsOpen && nodeRaw->getCanKick(); + bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick(); if (PALIsOpen) { // Increase minimumBytesPerAvatar if the PAL is open @@ -291,14 +291,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes struct AvatarSortData { - AvatarSortData(const SharedNodePointer& nodeShared, AvatarData* avatarData, quint64 lastEncodeTime) - : _nodeShared(nodeShared) - , _node(nodeShared.data()) + AvatarSortData(const Node* node, AvatarData* avatarData, quint64 lastEncodeTime) + : _node(node) , _avatarData(avatarData) , _lastEncodeTime(lastEncodeTime) { } - const SharedNodePointer& _nodeShared; - Node* _node; + const Node* _node; AvatarData* _avatarData; quint64 _lastEncodeTime; }; @@ -315,7 +313,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarData* otherAvatar = otherNodeData->getAvatarSharedPointer().get(); auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(otherAvatar->getSessionUUID()); - avatarsToSort.emplace_back(AvatarSortData(otherNode, otherAvatar, lastEncodeTime)); + avatarsToSort.emplace_back(AvatarSortData(otherNodeRaw, otherAvatar, lastEncodeTime)); } }); @@ -326,7 +324,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) : _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {} glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); } float getRadius() const override { - glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale()); + glm::vec3 nodeBoxHalfScale = (_avatar->getClientGlobalPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale()); return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); } uint64_t getTimestamp() const override { @@ -350,7 +348,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // ignore or sort for (const auto& avatar : avatarsToSort) { - if (avatar._node == nodeRaw) { + auto avatarNode = avatar._node; + if (avatarNode == destinationNode) { // don't echo updates to self continue; } @@ -362,7 +361,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // happen if for example the avatar is connected on a desktop and sending // updates at ~30hz. So every 3 frames we skip a frame. - auto avatarNode = avatar._node; assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map const AvatarMixerClientData* avatarNodeData = reinterpret_cast(avatarNode->getLinkedData()); @@ -372,13 +370,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // make sure we have data for this avatar, that it isn't the same node, // and isn't an avatar that the viewing node has ignored // or that has ignored the viewing node - if ((nodeRaw->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) - || (avatarNode->isIgnoringNodeWithID(nodeRaw->getUUID()) && !getsAnyIgnored)) { + if ((destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) + || (avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) { shouldIgnore = true; } else { // Check to see if the space bubble is enabled // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored - if (nodeRaw->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { + if (destinationNode->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { float sensorToWorldScale = avatarNodeData->getAvatarSharedPointer()->getSensorToWorldScale(); // Define the scale of the box for the current other node glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f * sensorToWorldScale; @@ -394,7 +392,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // Perform the collision check between the two bounding boxes if (nodeBox.touches(otherNodeBox)) { - nodeData->ignoreOther(node, avatar._nodeShared); + nodeData->ignoreOther(destinationNode, avatarNode); shouldIgnore = !getsAnyIgnored; } } @@ -576,7 +574,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.numBytesSent += numAvatarDataBytes; // send the avatar data PacketList - nodeList->sendPacketList(std::move(avatarPacketList), *nodeRaw); + nodeList->sendPacketList(std::move(avatarPacketList), *destinationNode); // record the bytes sent for other avatar data in the AvatarMixerClientData nodeData->recordSentAvatarData(numAvatarDataBytes); @@ -586,7 +584,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (traitsPacketList->getNumPackets() >= 1) { // send the traits packet list - nodeList->sendPacketList(std::move(traitsPacketList), *nodeRaw); + nodeList->sendPacketList(std::move(traitsPacketList), *destinationNode); } // record the number of avatars held back this frame From a5c3450bfcad201b95d044cae6cb0aeff90f63f1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 30 Aug 2018 13:43:31 +1200 Subject: [PATCH 150/744] Tweak mini tablet UI layout --- .../system/html/css/img/mt-bubble-a-hover.svg | 4 ++-- .../html/css/img/mt-bubble-a-normal.svg | 4 ++-- .../system/html/css/img/mt-bubble-i-hover.svg | 4 ++-- .../html/css/img/mt-bubble-i-normal.svg | 4 ++-- .../system/html/css/img/mt-expand-hover.svg | 24 +++++++++---------- .../system/html/css/img/mt-expand-normal.svg | 24 +++++++++---------- scripts/system/html/css/img/mt-mute-hover.svg | 4 ++-- .../system/html/css/img/mt-mute-normal.svg | 4 ++-- scripts/system/html/css/miniTablet.css | 13 ++++------ 9 files changed, 40 insertions(+), 45 deletions(-) diff --git a/scripts/system/html/css/img/mt-bubble-a-hover.svg b/scripts/system/html/css/img/mt-bubble-a-hover.svg index 36abbf5c34..88ddfff808 100644 --- a/scripts/system/html/css/img/mt-bubble-a-hover.svg +++ b/scripts/system/html/css/img/mt-bubble-a-hover.svg @@ -1,5 +1,5 @@  - - + + diff --git a/scripts/system/html/css/img/mt-bubble-a-normal.svg b/scripts/system/html/css/img/mt-bubble-a-normal.svg index e6e8021bf5..3d9d0a1286 100644 --- a/scripts/system/html/css/img/mt-bubble-a-normal.svg +++ b/scripts/system/html/css/img/mt-bubble-a-normal.svg @@ -1,5 +1,5 @@  - - + + diff --git a/scripts/system/html/css/img/mt-bubble-i-hover.svg b/scripts/system/html/css/img/mt-bubble-i-hover.svg index e39dff3888..c8c407139b 100644 --- a/scripts/system/html/css/img/mt-bubble-i-hover.svg +++ b/scripts/system/html/css/img/mt-bubble-i-hover.svg @@ -1,5 +1,5 @@  - - + + diff --git a/scripts/system/html/css/img/mt-bubble-i-normal.svg b/scripts/system/html/css/img/mt-bubble-i-normal.svg index ef2591ba16..6be11c464e 100644 --- a/scripts/system/html/css/img/mt-bubble-i-normal.svg +++ b/scripts/system/html/css/img/mt-bubble-i-normal.svg @@ -1,5 +1,5 @@  - - + + diff --git a/scripts/system/html/css/img/mt-expand-hover.svg b/scripts/system/html/css/img/mt-expand-hover.svg index eea3487bd8..97737f42c0 100644 --- a/scripts/system/html/css/img/mt-expand-hover.svg +++ b/scripts/system/html/css/img/mt-expand-hover.svg @@ -5,26 +5,26 @@ - - - + + + - - - + + + - - - + + + - - - + + + diff --git a/scripts/system/html/css/img/mt-expand-normal.svg b/scripts/system/html/css/img/mt-expand-normal.svg index 7b5b59a7c9..8b849ecc70 100644 --- a/scripts/system/html/css/img/mt-expand-normal.svg +++ b/scripts/system/html/css/img/mt-expand-normal.svg @@ -5,26 +5,26 @@ - - - + + + - - - + + + - - - + + + - - - + + + diff --git a/scripts/system/html/css/img/mt-mute-hover.svg b/scripts/system/html/css/img/mt-mute-hover.svg index 59c38ca5a0..909ecb0272 100644 --- a/scripts/system/html/css/img/mt-mute-hover.svg +++ b/scripts/system/html/css/img/mt-mute-hover.svg @@ -1,5 +1,5 @@  - - + + diff --git a/scripts/system/html/css/img/mt-mute-normal.svg b/scripts/system/html/css/img/mt-mute-normal.svg index 1f25bce9c6..506756216f 100644 --- a/scripts/system/html/css/img/mt-mute-normal.svg +++ b/scripts/system/html/css/img/mt-mute-normal.svg @@ -1,5 +1,5 @@  - - + + diff --git a/scripts/system/html/css/miniTablet.css b/scripts/system/html/css/miniTablet.css index e633f93664..3b01a45613 100644 --- a/scripts/system/html/css/miniTablet.css +++ b/scripts/system/html/css/miniTablet.css @@ -26,11 +26,11 @@ body { section { background-color: #404040; position: relative; - padding: 13px; + padding: 13px 9px; } .button { - width: 100%; + width: 128px; height: 90px; margin-top: 10px; text-align: center; @@ -41,7 +41,7 @@ section { } img { - width: 44px; + width: 46px; } #mute { @@ -78,14 +78,9 @@ img { background-image: url("./img/mt-bubble-a-hover.svg"); } - #bubble img { - position: relative; - left: -2px; - } - #expand { position: absolute; - right: 14px; + right: 12px; bottom: 13px; width: 40px; height: 40px; From 6583864597136a7b41ab7abe0613b825333510b7 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 29 Aug 2018 19:19:20 -0700 Subject: [PATCH 151/744] AVX2 implementation of interleave_4x4() --- libraries/audio/src/AudioHRTF.cpp | 6 +++- libraries/audio/src/avx2/AudioHRTF_avx2.cpp | 37 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index 0f87df2346..655527a65c 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -453,8 +453,12 @@ static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* ds (*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch } +void interleave_4x4_AVX2(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames); + static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { - interleave_4x4_SSE(src0, src1, src2, src3, dst, numFrames); + + static auto f = cpuSupportsAVX2() ? interleave_4x4_AVX2 : interleave_4x4_SSE; + (*f)(src0, src1, src2, src3, dst, numFrames); // dispatch } void biquad2_4x4_AVX2(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames); diff --git a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp index 0c3c6b5096..9000df367f 100644 --- a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp +++ b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp @@ -87,6 +87,43 @@ void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3 _mm256_zeroupper(); } +// 4 channel planar to interleaved +void interleave_4x4_AVX2(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { + + assert(numFrames % 8 == 0); + + for (int i = 0; i < numFrames; i += 8) { + + __m256 x0 = _mm256_loadu_ps(&src0[i]); + __m256 x1 = _mm256_loadu_ps(&src1[i]); + __m256 x2 = _mm256_loadu_ps(&src2[i]); + __m256 x3 = _mm256_loadu_ps(&src3[i]); + + // interleave (4x4 matrix transpose) + __m256 t0 = _mm256_unpacklo_ps(x0, x1); + __m256 t1 = _mm256_unpackhi_ps(x0, x1); + __m256 t2 = _mm256_unpacklo_ps(x2, x3); + __m256 t3 = _mm256_unpackhi_ps(x2, x3); + + x0 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(1,0,1,0)); + x1 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(3,2,3,2)); + x2 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(1,0,1,0)); + x3 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(3,2,3,2)); + + t0 = _mm256_permute2f128_ps(x0, x1, 0x20); + t1 = _mm256_permute2f128_ps(x2, x3, 0x20); + t2 = _mm256_permute2f128_ps(x0, x1, 0x31); + t3 = _mm256_permute2f128_ps(x2, x3, 0x31); + + _mm256_storeu_ps(&dst[4*i+0], t0); + _mm256_storeu_ps(&dst[4*i+8], t1); + _mm256_storeu_ps(&dst[4*i+16], t2); + _mm256_storeu_ps(&dst[4*i+24], t3); + } + + _mm256_zeroupper(); +} + // process 2 cascaded biquads on 4 channels (interleaved) // biquads are computed in parallel, by adding one sample of delay void biquad2_4x4_AVX2(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { From ec20fb7edb21cbb0c9af41163c70eff93217826f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 30 Aug 2018 16:16:49 +1200 Subject: [PATCH 152/744] Reorient and reposition mini tablet --- scripts/system/miniTablet.js | 59 ++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 403511365b..b91b016caf 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -20,24 +20,22 @@ proxyOverlay = null, PROXY_MODEL = Script.resolvePath("./assets/models/tinyTablet.fbx"), PROXY_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0045 }, // Proportional to tablet proper. - PROXY_POSITION_LEFT_HAND = { - x: 0, - y: 0.1, // Distance from joint. - z: 0.07 // Distance above palm. - }, - PROXY_POSITION_RIGHT_HAND = { - x: 0, - y: 0.1, // Distance from joint. - z: 0.07 // Distance above palm. - }, - /* - // Aligned cross-palm. - PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 90 }), - PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: -90 }), - */ - // Aligned with palm. - PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: -40, y: 180, z: 0 }), - PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: -40, y: 180, z: 0 }), + PROXY_POSITIONS = [ + { + x: -0.03, // Distance across hand. + y: 0.08, // Distance from joint. + z: 0.06 // Distance above palm. + }, + { + x: 0.03, // Distance across hand. + y: 0.08, // Distance from joint. + z: 0.06 // Distance above palm. + } + ], + PROXY_ROTATIONS = [ + Quat.fromVec3Degrees({ x: 0, y: 180 - 40, z: 90 }), + Quat.fromVec3Degrees({ x: 0, y: 180 + 40, z: -90 }), + ], // UI overlay. proxyUIOverlay = null, @@ -76,10 +74,10 @@ proxyScaleTimer = null, proxyScaleStart, PROXY_EXPAND_HANDLES = [ // Normalized coordinates in range [-0.5, 0.5] about center of mini tablet. - { x: 0.5, y: -0.75, z: 0 }, - { x: -0.5, y: -0.75, z: 0 } + { x: 0.5, y: -0.65, z: 0 }, + { x: -0.5, y: -0.65, z: 0 } ], - PROXY_EXPAND_DELTA_ROTATION = Quat.fromVec3Degrees({ x: -5, y: 0, z: 0 }), + PROXY_EXPAND_DELTA_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }), proxyExpandHand, proxyExpandLocalPosition, proxyExpandLocalRotation = Quat.IDENTITY, @@ -284,9 +282,8 @@ Overlays.editOverlay(proxyOverlay, { parentID: MyAvatar.SELF_ID, parentJointIndex: handJointIndex(proxyHand), - localPosition: Vec3.multiply(avatarScale, - proxyHand === LEFT_HAND ? PROXY_POSITION_LEFT_HAND : PROXY_POSITION_RIGHT_HAND), - localRotation: proxyHand === LEFT_HAND ? PROXY_ROTATION_LEFT_HAND : PROXY_ROTATION_RIGHT_HAND, + localPosition: Vec3.multiply(avatarScale, PROXY_POSITIONS[proxyHand]), + localRotation: PROXY_ROTATIONS[proxyHand], dimensions: Vec3.multiply(initialScale, PROXY_DIMENSIONS), visible: true }); @@ -402,12 +399,14 @@ } function shouldShowProxy(hand) { - // Should show tablet proxy if hand is oriented toward the camera and the camera is oriented toward the proxy tablet. + // Should show proxy if it would be oriented toward the camera. var pose, jointIndex, handPosition, handOrientation, - cameraToHandDirection; + proxyPosition, + proxyOrientation, + proxyToCameraDirection; pose = Controller.getPoseValue(hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand); if (!pose.valid) { @@ -418,9 +417,11 @@ handPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex))); handOrientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex)); - cameraToHandDirection = Vec3.normalize(Vec3.subtract(handPosition, Camera.position)); - - return Vec3.dot(cameraToHandDirection, Quat.getForward(handOrientation)) > MIN_HAND_CAMERA_ANGLE_COS; + proxyPosition = Vec3.sum(handPosition, Vec3.multiply(avatarScale, + Vec3.multiplyQbyV(handOrientation, PROXY_POSITIONS[hand]))); + proxyOrientation = Quat.multiply(handOrientation, PROXY_ROTATIONS[hand]); + proxyToCameraDirection = Vec3.normalize(Vec3.subtract(Camera.position, proxyPosition)); + return Vec3.dot(proxyToCameraDirection, Quat.getForward(proxyOrientation)) > MIN_HAND_CAMERA_ANGLE_COS; } function enterProxyHidden() { From 6c502e79e350924d5006d457c22ea85f8995d0d9 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Wed, 29 Aug 2018 22:56:44 -0700 Subject: [PATCH 153/744] Fine tuning a LOD regulator PV that works --- interface/src/LODManager.cpp | 102 ++++-------- interface/src/LODManager.h | 15 +- .../utilities/lib/plotperf/PlotPerf.qml | 6 +- scripts/developer/utilities/render/lod.qml | 153 +++++++++++------- 4 files changed, 139 insertions(+), 137 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index df371f3a59..df3b9a9f69 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -66,14 +66,23 @@ void LODManager::setRenderTimes(float presentTime, float engineRunTime, float ba } void LODManager::autoAdjustLOD(float realTimeDelta) { - // float maxRenderTime = glm::max(glm::max(_presentTime, _engineRunTime), _gpuTime); - float maxRenderTime = glm::max(glm::max(_presentTime, _engineRunTime), _gpuTime); + // The "render time" is the worse of: + // - engineRunTime: Time spent in the render thread in the engine producing the gpu::Frame N + // - batchTime: Time spent in the present thread processing the batches of gpu::Frame N+1 + // - presentTime: Time spent in the present thread between the last 2 swap buffers considered the total time to submit gpu::Frame N+1 + // - gpuTime: Time spent in the GPU executing the gpu::Frame N + 2 + + // But Present time is in reality synched with the monitor/display refresh rate, it s always longer than batchTime. + // So if batchTime is fast enough relative to PResent Time we are using it, otherwise we are using presentTime. got it ? + auto presentTime = (_presentTime - _batchTime > 3.0f ? _batchTime + 3.0f : _presentTime); + float maxRenderTime = glm::max(glm::max(presentTime, _engineRunTime), _gpuTime); + // compute time-weighted running average maxRenderTime // Note: we MUST clamp the blend to 1.0 for stability float blend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * maxRenderTime; // msec - float smoothBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE * _pidCoefs.w) ? realTimeDelta / (LOD_ADJUST_RUNNING_AVG_TIMESCALE * _pidCoefs.w) : 1.0f; + float smoothBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) ? realTimeDelta / (LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) : 1.0f; _smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec @@ -87,24 +96,29 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { float oldSolidAngle = getLODAngleDeg(); float targetFPS = 0.5 * (getLODDecreaseFPS() + getLODIncreaseFPS()); - // float targetFPS = (getLODDecreaseFPS()); float targetPeriod = 1.0f / targetFPS; float currentFPS = (float)MSECS_PER_SECOND / _avgRenderTime; + float currentSmoothFPS = (float)MSECS_PER_SECOND / _smoothRenderTime; + float currentVarianceFPS = (currentSmoothFPS - currentFPS); + currentVarianceFPS *= currentVarianceFPS; - static uint64_t lastTime = usecTimestampNow(); + auto dt = realTimeDelta; - uint64_t now = usecTimestampNow(); - auto dt = (float) ((now - lastTime) / double(USECS_PER_MSEC)); - // if (dt < targetPeriod * _pidCoefs.w) return; - dt = realTimeDelta; - - lastTime = now; auto previous_error = _pidHistory.x; auto previous_integral = _pidHistory.y; - auto error = (targetFPS - currentFPS) / targetFPS; - error = glm::clamp(error, -1.0f, 1.0f); + auto smoothError = (targetFPS - currentSmoothFPS); + + auto fpsError = smoothError; + + auto errorSquare = smoothError * smoothError; + + auto noiseCoef = (errorSquare < _pidCoefs.w * currentVarianceFPS ? 0.0f : 1.0f); + + auto normalizedError = noiseCoef * smoothError / targetFPS; + + auto error = glm::clamp(normalizedError, -1.0f, 1.0f); auto integral = previous_integral + error * dt; glm::clamp(integral, -1.0f, 1.0f); @@ -131,59 +145,6 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { setLODAngleDeg(newSolidAngle); - //newSolidAngle = std::max( 0.5f, std::min(newSolidAngle, 90.f)); - - //auto halTan = glm::tan(glm::radians(newSolidAngle * 0.5f)); - - //auto octreeSizeScale = TREE_SCALE * OCTREE_TO_MESH_RATIO / halTan; - // _octreeSizeScale = octreeSizeScale; -/* - if (currentFPS < getLODDecreaseFPS()) { - if (now > _decreaseFPSExpiry) { - _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - if (_octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale *= LOD_AUTO_ADJUST_DECREMENT_FACTOR; - if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; - } - emit LODDecreased(); - emit LODChanged(); - // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime - // to provide an FPS just above the decrease threshold. It will drift close to its - // true value after a few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary. - _avgRenderTime = (float)MSECS_PER_SECOND / (getLODDecreaseFPS() + 1.0f); - } - _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - } - _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - } else if (currentFPS > getLODIncreaseFPS()) { - if (now > _increaseFPSExpiry) { - _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { - if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; - } else { - _octreeSizeScale *= LOD_AUTO_ADJUST_INCREMENT_FACTOR; - } - if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; - } - emit LODIncreased(); - emit LODChanged(); - - // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime - // to provide an FPS just below the increase threshold. It will drift close to its - // true value after a few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary. - _avgRenderTime = (float)MSECS_PER_SECOND / (getLODIncreaseFPS() - 1.0f); - } - _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - } - _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - } else { - _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - _decreaseFPSExpiry = _increaseFPSExpiry; - } -*/ if (oldOctreeSizeScale != _octreeSizeScale) { auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); if (lodToolsDialog) { @@ -325,6 +286,11 @@ void LODManager::saveSettings() { hmdLODDecreaseFPS.set(getHMDLODDecreaseFPS()); } + +void LODManager::setSmoothScale(float t) { + _smoothScale = glm::max(1.0f, t); +} + void LODManager::setWorldDetailQuality(float quality) { static const float MAX_DESKTOP_FPS = 60; @@ -414,7 +380,7 @@ float LODManager::getPidKi() const { float LODManager::getPidKd() const { return _pidCoefs.z; } -float LODManager::getPidT() const { +float LODManager::getPidKv() const { return _pidCoefs.w; } void LODManager::setPidKp(float k) { @@ -426,7 +392,7 @@ void LODManager::setPidKi(float k) { void LODManager::setPidKd(float k) { _pidCoefs.z = k; } -void LODManager::setPidT(float t) { +void LODManager::setPidKv(float t) { _pidCoefs.w = t; } diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 167533a236..6843744d16 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -67,6 +67,7 @@ class LODManager : public QObject, public Dependency { Q_PROPERTY(float smoothRenderTime READ getSmoothRenderTime) Q_PROPERTY(float smoothFPS READ getSmoothFPS) + Q_PROPERTY(float smoothScale READ getSmoothScale WRITE setSmoothScale) Q_PROPERTY(float lodLevel READ getLODLevel WRITE setLODLevel NOTIFY LODChanged) Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) @@ -80,7 +81,7 @@ class LODManager : public QObject, public Dependency { Q_PROPERTY(float pidKp READ getPidKp WRITE setPidKp) Q_PROPERTY(float pidKi READ getPidKi WRITE setPidKi) Q_PROPERTY(float pidKd READ getPidKd WRITE setPidKd) - Q_PROPERTY(float pidT READ getPidT WRITE setPidT) + Q_PROPERTY(float pidKv READ getPidKv WRITE setPidKv) Q_PROPERTY(float pidOp READ getPidOp) Q_PROPERTY(float pidOi READ getPidOi) @@ -195,6 +196,9 @@ public: void saveSettings(); void resetLODAdjust(); + void setSmoothScale(float t); + float getSmoothScale() const { return _smoothScale; } + float getAverageRenderTime() const { return _avgRenderTime; }; float getMaxTheoreticalFPS() const { return (float)MSECS_PER_SECOND / _avgRenderTime; }; @@ -216,12 +220,12 @@ public: float getPidKp() const; float getPidKi() const; float getPidKd() const; - float getPidT() const; + float getPidKv() const; void setPidKp(float k); void setPidKi(float k); void setPidKd(float k); - void setPidT(float t); - + void setPidKv(float t); + float getPidOp() const; float getPidOi() const; float getPidOd() const; @@ -254,6 +258,7 @@ private: float _batchTime{ 0.0f }; // msec float _gpuTime { 0.0f }; // msec + float _smoothScale{ 8.0f }; float _avgRenderTime { 0.0f }; // msec float _smoothRenderTime{ 0.0f }; float _desktopMaxRenderTime { DEFAULT_DESKTOP_MAX_RENDER_TIME }; @@ -262,7 +267,7 @@ private: float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; - glm::vec4 _pidCoefs{ 1.0f, 0.0000000f, 0.f, 8.0f }; + glm::vec4 _pidCoefs{ 1.0f, 0.0f, 0.0f, 1.0f }; // Kp, Ki, Kd, Kv glm::vec4 _pidHistory{ 0.0f }; glm::vec4 _pidOutputs{ 0.0f }; diff --git a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml index 27101a2867..f239cc010a 100644 --- a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml +++ b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml @@ -217,9 +217,9 @@ Item { function displayTitle(ctx, text, maxVal) { ctx.fillStyle = "grey"; ctx.textAlign = "right"; - ctx.fillText("max" + displayValue(_displayMaxValue, root.valueUnit), width, pixelFromVal(_displayMaxValue)); + ctx.fillText("max " + displayValue(_displayMaxValue, root.valueUnit), width, pixelFromVal(_displayMaxValue)); - ctx.fillText("min" + displayValue(_displayMinValue, root.valueUnit), width, pixelFromVal(_displayMinValue)); + ctx.fillText("min " + displayValue(_displayMinValue, root.valueUnit), width, pixelFromVal(_displayMinValue)); ctx.fillStyle = "white"; ctx.textAlign = "left"; @@ -245,7 +245,6 @@ Item { ctx.strokeStyle= "LightSlateGray"; ctx.lineWidth="1"; - // ctx.strokeStyle= "grey"; ctx.beginPath(); ctx.moveTo(0, maxY); ctx.lineTo(width, maxY); @@ -254,7 +253,6 @@ Item { if (_displayMinValue != 0) { var zeroY = pixelFromVal(0); var minY = pixelFromVal(_displayMinValue); - // ctx.strokeStyle= "DarkRed"; ctx.beginPath(); ctx.moveTo(0, zeroY); ctx.lineTo(width, zeroY); diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index d0562e1020..28cf744394 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -53,18 +53,26 @@ Item { min: 0.25 integral: false - anchors.left: autoLOD.left + anchors.left: parent.left anchors.right: parent.right } - HifiControls.CheckBox { - id: autoLOD - boxSize: 20 - text: "Auto LOD" - checked: LODManager.automaticLODAdjust - onCheckedChanged: { LODManager.automaticLODAdjust = (checked) } + Row { + HifiControls.CheckBox { + id: autoLOD + boxSize: 20 + text: "Auto LOD" + checked: LODManager.automaticLODAdjust + onCheckedChanged: { LODManager.automaticLODAdjust = (checked) } + } + HifiControls.CheckBox { + id: showLODRegulatorDetails + visible: LODManager.automaticLODAdjust + boxSize: 20 + text: "Show LOD Details" + } } - + RichSlider { visible: !LODManager.automaticLODAdjust showLabel: true @@ -78,67 +86,85 @@ Item { anchors.left: parent.left anchors.right: parent.right } - - RichSlider { - visible: LODManager.automaticLODAdjust - showLabel: true - label: "LOD PID Kp" - valueVar: LODManager["pidKp"] - valueVarSetter: (function (v) { LODManager["pidKp"] = v }) - max: 2.0 - min: 0.0 - integral: false - numDigits: 3 - + Column { + id: lodRegulatorDetails + visible: LODManager.automaticLODAdjust && showLODRegulatorDetails.checked anchors.left: parent.left anchors.right: parent.right - } - RichSlider { - visible: LODManager.automaticLODAdjust - showLabel: true - label: "LOD PID Ki" - valueVar: LODManager["pidKi"] - valueVarSetter: (function (v) { LODManager["pidKi"] = v }) - max: 0.1 - min: 0.0 - integral: false - numDigits: 8 + RichSlider { + visible: lodRegulatorDetails.visible + showLabel: true + label: "LOD Kp" + valueVar: LODManager["pidKp"] + valueVarSetter: (function (v) { LODManager["pidKp"] = v }) + max: 2.0 + min: 0.0 + integral: false + numDigits: 3 - anchors.left: parent.left - anchors.right: parent.right - } - RichSlider { - visible: LODManager.automaticLODAdjust - showLabel: true - label: "LOD PID Kd" - valueVar: LODManager["pidKd"] - valueVarSetter: (function (v) { LODManager["pidKd"] = v }) - max: 10.0 - min: 0.0 - integral: false - numDigits: 3 + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: false && lodRegulatorDetails.visible + showLabel: true + label: "LOD Ki" + valueVar: LODManager["pidKi"] + valueVarSetter: (function (v) { LODManager["pidKi"] = v }) + max: 0.1 + min: 0.0 + integral: false + numDigits: 8 - anchors.left: parent.left - anchors.right: parent.right - } - RichSlider { - visible: LODManager.automaticLODAdjust - showLabel: true - label: "LOD PID Num T" - valueVar: LODManager["pidT"] - valueVarSetter: (function (v) { LODManager["pidT"] = v }) - max: 10.0 - min: 0.0 - integral: true + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: false && lodRegulatorDetails.visible + showLabel: true + label: "LOD Kd" + valueVar: LODManager["pidKd"] + valueVarSetter: (function (v) { LODManager["pidKd"] = v }) + max: 10.0 + min: 0.0 + integral: false + numDigits: 3 - anchors.left: parent.left - anchors.right: parent.right + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: lodRegulatorDetails.visible + showLabel: true + label: "LOD Kv" + valueVar: LODManager["pidKv"] + valueVarSetter: (function (v) { LODManager["pidKv"] = v }) + max: 2.0 + min: 0.0 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: lodRegulatorDetails.visible + showLabel: true + label: "LOD Smooth Scale" + valueVar: LODManager["smoothScale"] + valueVarSetter: (function (v) { LODManager["smoothScale"] = v }) + max: 20.0 + min: 1.0 + integral: true + + anchors.left: parent.left + anchors.right: parent.right + } } } Column { id: stats - spacing: 8 + spacing: 4 anchors.right: parent.right anchors.left: parent.left anchors.top: topHeader.bottom @@ -146,7 +172,8 @@ Item { function evalEvenHeight() { // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? - return (height - topLine.height - bottomLine.height - spacing * (children.length - 3)) / (children.length - 2) + var numPlots = (children.length + (lodRegulatorDetails.visible ? 1 : 0) - 2) + return (height - topLine.height - bottomLine.height - spacing * (numPlots - 1)) / (numPlots) } Separator { @@ -226,6 +253,7 @@ Item { ] } PlotPerf { + // visible: lodRegulatorDetails.visible title: "PID Output" height: parent.evalEvenHeight() object: LODManager @@ -248,6 +276,11 @@ Item { prop: "pidOd", label: "Od", color: "#FF6666" + }, + { + prop: "pidO", + label: "Output", + color: "#66FF66" } ] } From a4bb8cb6221747730b27cf6de3ca6e7f41567255 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 30 Aug 2018 09:08:02 -0700 Subject: [PATCH 154/744] Add comment on future of PickTransformNodes --- libraries/pointers/src/PickTransformNode.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/pointers/src/PickTransformNode.h b/libraries/pointers/src/PickTransformNode.h index 7547d3f181..b4c92f3ba7 100644 --- a/libraries/pointers/src/PickTransformNode.h +++ b/libraries/pointers/src/PickTransformNode.h @@ -10,6 +10,7 @@ #include "TransformNode.h" +// TODO: Remove this class when Picks are converted to SpatiallyNestables class PickTransformNode : public TransformNode { public: PickTransformNode(unsigned int uid); From 6c4862910e70fe0236b8ce9e4539a03c205fe802 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 30 Aug 2018 10:13:35 -0700 Subject: [PATCH 155/744] smoother edges for workload region feedback response --- libraries/workload/src/workload/ViewTask.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/workload/src/workload/ViewTask.cpp b/libraries/workload/src/workload/ViewTask.cpp index 27b136d64e..9d0c693149 100644 --- a/libraries/workload/src/workload/ViewTask.cpp +++ b/libraries/workload/src/workload/ViewTask.cpp @@ -135,15 +135,15 @@ glm::vec2 Regulator::run(const Timing_ns& deltaTime, const Timing_ns& measuredTi float noise = sqrtf(_measuredTimeNoiseSquared); // check budget - float budgetDelta = _budget.count() - _measuredTimeAverage; - if (abs(budgetDelta) < noise) { - // budget is within the noise + float offsetFromTarget = _budget.count() - _measuredTimeAverage; + if (fabsf(offsetFromTarget) < noise) { + // budget is within the noise --> do nothing return currentFrontBack; } - // clamp the step factor - glm::vec2 stepDelta = budgetDelta < 0.0f ? -_relativeStepDown : _relativeStepUp; - + // compute response + glm::vec2 stepDelta = offsetFromTarget < 0.0f ? -_relativeStepDown : _relativeStepUp; + stepDelta *= glm::min(1.0f, (fabsf(offsetFromTarget) - noise) / noise); // ease out of "do nothing" return currentFrontBack * (1.0f + stepDelta); } From c2ac2b9ab0f56aae0e8e283d2508cce10f0df0b3 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 30 Aug 2018 10:59:55 -0700 Subject: [PATCH 156/744] Various tweaks; fix clang warning --- .../src/avatars/AvatarMixerClientData.cpp | 2 +- .../src/avatars/AvatarMixerClientData.h | 2 +- .../src/avatars/AvatarMixerSlave.cpp | 16 +++++++--------- libraries/shared/src/SharedUtil.cpp | 6 +++--- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 1dd4cc769b..c54db90f5d 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -239,7 +239,7 @@ void AvatarMixerClientData::ignoreOther(const Node* self, const Node* other) { } } -void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other) { +void AvatarMixerClientData::removeFromRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.erase(other); } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 7d203bd771..ee27f9bb0f 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -93,7 +93,7 @@ public: glm::vec3 getGlobalBoundingBoxCorner() const { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); } bool isRadiusIgnoring(const QUuid& other) const { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); } void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); } - void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other); + void removeFromRadiusIgnoringSet(const QUuid& other); void ignoreOther(SharedNodePointer self, SharedNodePointer other); void ignoreOther(const Node* self, const Node* other); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index e772fa4d08..34a429bea0 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -276,16 +276,16 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); // Define the minimum bubble size - static const glm::vec3 minBubbleSize = avatar.getSensorToWorldScale() * glm::vec3(0.3f, 1.3f, 0.3f); + const glm::vec3 minBubbleSize = avatar.getSensorToWorldScale() * glm::vec3(0.3f, 1.3f, 0.3f); // Define the scale of the box for the current node - glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f * avatar.getSensorToWorldScale(); + glm::vec3 nodeBoxScale = (avatar.getClientGlobalPosition() - avatar.getGlobalBoundingBoxCorner()) * 2.0f * avatar.getSensorToWorldScale(); // Set up the bounding box for the current node - AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale); + AABox nodeBox(avatar.getGlobalBoundingBoxCorner(), nodeBoxScale); // Clamp the size of the bounding box to a minimum scale if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) { nodeBox.setScaleStayCentered(minBubbleSize); } - // Quadruple the scale of both bounding boxes + // Quadruple the scale of first bounding box nodeBox.embiggen(4.0f); @@ -386,7 +386,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { otherNodeBox.setScaleStayCentered(minBubbleSize); } - // Change the scale of both bounding boxes + // Change the scale of other bounding box // (This is an arbitrary number determined empirically) otherNodeBox.embiggen(2.4f); @@ -398,7 +398,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } // Not close enough to ignore if (!shouldIgnore) { - nodeData->removeFromRadiusIgnoringSet(node, avatarNode->getUUID()); + nodeData->removeFromRadiusIgnoringSet(avatarNode->getUUID()); } } @@ -439,6 +439,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); while (!sortedAvatars.empty()) { const Node* otherNode = sortedAvatars.top().getNode(); + auto lastEncodeForOther = sortedAvatars.top().getTimestamp(); sortedAvatars.pop(); remainingAvatars--; @@ -490,7 +491,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } bool includeThisAvatar = true; - auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); lastSentJointsForOther.resize(otherAvatar->getJointCount()); @@ -561,8 +561,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // use helper to add any changed traits to our packet list traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); - - traitsPacketList->getDataSize(); } quint64 startPacketSending = usecTimestampNow(); diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 3bef7f7e18..a36bdb061e 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -128,9 +128,9 @@ void usecTimestampNowForceClockSkew(qint64 clockSkew) { ::usecTimestampNowAdjust = clockSkew; } -static std::atomic TIME_REFERENCE { 0 }; // in usec -static std::once_flag usecTimestampNowIsInitialized; -static QElapsedTimer timestampTimer; +//static std::atomic TIME_REFERENCE { 0 }; // in usec +//static std::once_flag usecTimestampNowIsInitialized; +//static QElapsedTimer timestampTimer; quint64 usecTimestampNow(bool wantDebug) { using namespace std::chrono; From b45b0f54e4ebd58d6ebe87e67c10a1089902d8bf Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 30 Aug 2018 11:59:53 -0700 Subject: [PATCH 157/744] Move unfilteredHandlers processing back into main loop --- libraries/networking/src/udt/Socket.cpp | 36 ++++++++++--------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index d764b9e8b2..44220df8f8 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -361,28 +361,6 @@ void Socket::processPendingDatagrams(int) { // setup a HifiSockAddr to read into HifiSockAddr senderSockAddr; - // Process unfiltered packets first. - for (auto datagramIter = _incomingDatagrams.begin(); datagramIter != _incomingDatagrams.end(); ++datagramIter) { - senderSockAddr.setAddress(datagramIter->_senderAddress); - senderSockAddr.setPort(datagramIter->_senderPort); - auto it = _unfilteredHandlers.find(senderSockAddr); - if (it != _unfilteredHandlers.end()) { - // we have a registered unfiltered handler for this HifiSockAddr (eg. STUN packet) - call that and return - if (it->second) { - auto basePacket = BasePacket::fromReceivedPacket(std::move(datagramIter->_datagram), - datagramIter->_datagramLength, - senderSockAddr); - basePacket->setReceiveTime(datagramIter->_receiveTime); - it->second(std::move(basePacket)); - } - - datagramIter = _incomingDatagrams.erase(datagramIter); - if (datagramIter == _incomingDatagrams.end()) { - break; - } - } - } - while (!_incomingDatagrams.empty()) { auto& datagram = _incomingDatagrams.front(); senderSockAddr.setAddress(datagram._senderAddress); @@ -397,6 +375,20 @@ void Socket::processPendingDatagrams(int) { _lastPacketSizeRead = datagramSize; _lastPacketSockAddr = senderSockAddr; + // Process unfiltered packets first. + auto it = _unfilteredHandlers.find(senderSockAddr); + if (it != _unfilteredHandlers.end()) { + // we have a registered unfiltered handler for this HifiSockAddr (eg. STUN packet) - call that and return + if (it->second) { + auto basePacket = BasePacket::fromReceivedPacket(std::move(datagram._datagram), + datagramSize, senderSockAddr); + basePacket->setReceiveTime(receiveTime); + it->second(std::move(basePacket)); + } + _incomingDatagrams.pop_front(); + continue; + } + // check if this was a control packet or a data packet bool isControlPacket = *reinterpret_cast(datagram._datagram.get()) & CONTROL_BIT_MASK; From 78c7948ebc374f78666a1978593f9dc4e9884cf2 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 30 Aug 2018 12:11:43 -0700 Subject: [PATCH 158/744] fine tuning the noise coef ramp and documenting the algorithm --- interface/src/LODManager.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index df3b9a9f69..925d48f9cd 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -100,8 +100,12 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { float currentFPS = (float)MSECS_PER_SECOND / _avgRenderTime; float currentSmoothFPS = (float)MSECS_PER_SECOND / _smoothRenderTime; + + // Compute the Variance of the FPS signal (FPS - smouthFPS)^2 + // Also scale it by a percentage for fine tuning (default is 100%) float currentVarianceFPS = (currentSmoothFPS - currentFPS); currentVarianceFPS *= currentVarianceFPS; + currentVarianceFPS *= _pidCoefs.w; auto dt = realTimeDelta; @@ -114,11 +118,22 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { auto errorSquare = smoothError * smoothError; - auto noiseCoef = (errorSquare < _pidCoefs.w * currentVarianceFPS ? 0.0f : 1.0f); + // Define a noiseCoef that is trying to adjust the error to the FPS target value based on its strength + // relative to the current Variance of the FPS signal. + // If the error is within the variance, just set to 0. + // if its within 2x the variance scale the control + // and full control if error is bigger than 2x variance + auto noiseCoef = 1.0f; + if (errorSquare < currentVarianceFPS) { + noiseCoef = 0.0f; + } else if (errorSquare < 2.0f * currentVarianceFPS) { + noiseCoef = (errorSquare - currentVarianceFPS) / currentVarianceFPS; + } - auto normalizedError = noiseCoef * smoothError / targetFPS; - - auto error = glm::clamp(normalizedError, -1.0f, 1.0f); + // The final normalized error is the the error to the FPS target, weighted by the noiseCoef, then normailzed by the target FPS. + // it s also clamped in the [-1, 1] range + auto error = noiseCoef * smoothError / targetFPS; + error = glm::clamp(error, -1.0f, 1.0f); auto integral = previous_integral + error * dt; glm::clamp(integral, -1.0f, 1.0f); From 93b0f151166d818fb019e4e2e48353d112738fcd Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 30 Aug 2018 13:22:17 -0700 Subject: [PATCH 159/744] addressing some feedback --- scripts/system/interstitialPage.js | 74 +++++++++++++++++------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index cf06e222c9..86a9e744e9 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -18,13 +18,13 @@ var MAX_X_SIZE = 3.8; var EPSILON = 0.01; var isVisible = false; - var STABILITY = 3.0; var VOLUME = 0.4; var tune = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/crystals_and_voices.wav"); var sample = null; var MAX_LEFT_MARGIN = 1.9; var INNER_CIRCLE_WIDTH = 4.7; var DEFAULT_Z_OFFSET = 5.45; + var previousCameraMode = Camera.mode; var renderViewTask = Render.getConfig("RenderMainView"); var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); @@ -181,7 +181,7 @@ var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; var lastInterval = Date.now(); - var currentDomain = ""; + var currentDomain = "no domain"; var timer = null; var target = 0; @@ -228,6 +228,8 @@ target = 0; currentProgress = 0.1; connectionToDomainFailed = false; + previousCameraMode = Camera.mode; + Camera.mode = "first person"; timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); } } @@ -249,30 +251,42 @@ print("domain changed: " + domain); if (domain !== currentDomain) { MyAvatar.restoreAnimation(); - var name = AddressManager.placename; + var name = location.placename; domainName = name.charAt(0).toUpperCase() + name.slice(1); + var doRequest = true; + if (name.length === 0 && location.href === "file:///~/serverless/tutorial.json") { + domainName = "Serveless Domain (Tutorial)"; + doRequest = false; + } var domainNameLeftMargin = getLeftMargin(domainNameTextID, domainName); var textProperties = { text: domainName, leftMargin: domainNameLeftMargin }; - var url = Account.metaverseServerURL + '/api/v1/places/' + domain; - request({ - uri: url - }, function(error, data) { - if (data.status === "success") { - var domainInfo = data.data; - var domainDescriptionText = domainInfo.place.description; - print("domainText: " + domainDescriptionText); - var leftMargin = getLeftMargin(domainDescription, domainDescriptionText); - var domainDescriptionProperties = { - text: domainDescriptionText, - leftMargin: leftMargin - }; - Overlays.editOverlay(domainDescription, domainDescriptionProperties); - } - }); + if (doRequest) { + var url = Account.metaverseServerURL + '/api/v1/places/' + domain; + request({ + uri: url + }, function(error, data) { + if (data.status === "success") { + var domainInfo = data.data; + var domainDescriptionText = domainInfo.place.description; + print("domainText: " + domainDescriptionText); + var leftMargin = getLeftMargin(domainDescription, domainDescriptionText); + var domainDescriptionProperties = { + text: domainDescriptionText, + leftMargin: leftMargin + }; + Overlays.editOverlay(domainDescription, domainDescriptionProperties); + } + }); + } else { + var domainDescriptionProperties = { + text: "" + }; + Overlays.editOverlay(domainDescription, domainDescriptionProperties); + } var randomIndex = Math.floor(Math.random() * userTips.length); var tip = userTips[randomIndex]; @@ -320,8 +334,6 @@ visible: !physicsEnabled }; - // Menu.setIsOptionChecked("Show Overlays", physicsEnabled); - if (!HMD.active) { MyAvatar.headOrientation = Quat.multiply(Quat.cancelOutRollAndPitch(MyAvatar.headOrientation), Quat.fromPitchYawRollDegrees(-3.0, 0, 0)); } @@ -343,7 +355,9 @@ resetValues(); - Camera.mode = "first person"; + if (physicsEnabled) { + Camera.mode = previousCameraMode; + } } @@ -358,11 +372,6 @@ Overlays.editOverlay(anchorOverlay, { localPosition: localPosition }); } - - Window.interstitialStatusChanged.connect(function(interstitialMode) { - print("------> insterstitial mode changed " + interstitialMode + " <------"); - }); - function update() { var physicsEnabled = Window.isPhysicsEnabled(); var thisInterval = Date.now(); @@ -372,10 +381,13 @@ var domainLoadingProgressPercentage = Window.domainLoadingProgress(); var progress = MAX_X_SIZE * domainLoadingProgressPercentage; - print(progress); - //if (progress >= target) { + if (progress >= target) { target = progress; - //} + } + + if ((physicsEnabled && (currentProgress < MAX_X_SIZE))) { + target = MAX_X_SIZE; + } currentProgress = lerp(currentProgress, target, 0.2); var properties = { @@ -393,6 +405,7 @@ timer = null; return; } + timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); } @@ -400,7 +413,6 @@ location.hostChanged.connect(domainChanged); location.lookupResultsFinished.connect(function() { Script.setTimeout(function() { - print("location connected: " + location.isConnected); connectionToDomainFailed = !location.isConnected; }, 1200); }); From 4b7ca76ebdecd76950acb7b2f0daecc174a397bd Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 30 Aug 2018 14:15:57 -0700 Subject: [PATCH 160/744] Create correct folder. --- libraries/shared/src/shared/FileUtils.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index 9bdd65bf11..0709a53602 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -156,10 +156,12 @@ bool FileUtils::canCreateFile(const QString& fullPath) { qDebug(shared) << "unable to overwrite file '" << fullPath << "'"; return false; } - QDir dir(fileInfo.absolutePath()); + + QString absolutePath = fileInfo.absolutePath(); + QDir dir(absolutePath); if (!dir.exists()) { - if (!dir.mkpath(fullPath)) { - qDebug(shared) << "unable to create dir '" << dir.absolutePath() << "'"; + if (!dir.mkpath(absolutePath)) { + qDebug(shared) << "unable to create dir '" << absolutePath << "'"; return false; } } From 03ede9371e1a070700952e4c504c445f1a51022a Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 30 Aug 2018 14:26:39 -0700 Subject: [PATCH 161/744] cleaning code --- interface/src/octree/SafeLanding.cpp | 2 -- interface/src/scripting/WindowScriptingInterface.cpp | 3 --- interface/src/scripting/WindowScriptingInterface.h | 3 --- 3 files changed, 8 deletions(-) diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 8d36b3fcb0..6b7a0f582c 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -175,7 +175,6 @@ bool SafeLanding::entitiesRenderReady() { for (auto entityMapIter = _trackedEntitiesRenderStatus.begin(); entityMapIter != _trackedEntitiesRenderStatus.end(); ++entityMapIter) { auto entity = entityMapIter->second; bool visuallyReady = entity->isVisuallyReady(); - qDebug() << "is entityType: " << EntityTypes::getEntityTypeName(entity->getType()) << " " << visuallyReady << " " << entityMapIter->first; if (visuallyReady || !entityTree->renderableForEntityId(entityMapIter->first)) { entityMapIter = _trackedEntitiesRenderStatus.erase(entityMapIter); if (entityMapIter == _trackedEntitiesRenderStatus.end()) { @@ -185,7 +184,6 @@ bool SafeLanding::entitiesRenderReady() { entity->requestRenderUpdate(); } } - qDebug() << "list size: -> " << _trackedEntitiesRenderStatus.size(); return _trackedEntitiesRenderStatus.empty(); } diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index b4c1563795..9e13e2affb 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -53,9 +53,6 @@ WindowScriptingInterface::WindowScriptingInterface() { }); connect(qApp->getWindow(), &MainWindow::windowGeometryChanged, this, &WindowScriptingInterface::onWindowGeometryChanged); - connect(qApp, &Application::interstitialModeChanged, [this] (bool interstitialStatus) { - emit interstitialStatusChanged(interstitialStatus); - }); } WindowScriptingInterface::~WindowScriptingInterface() { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 3dcd8cfeed..626d142785 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -744,9 +744,6 @@ signals: */ void geometryChanged(QRect geometry); - - void interstitialStatusChanged(bool intersititalMode); - private: QString getPreviousBrowseLocation() const; void setPreviousBrowseLocation(const QString& location); From daf81bbcb8cb963ea5b020e0a70bf7640f7ca762 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Fri, 31 Aug 2018 00:29:06 +0300 Subject: [PATCH 162/744] FB17982 Avatar App reloads existing selected bookmark on app relaunch --- interface/resources/qml/hifi/AvatarApp.qml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index d8eae311d8..0c950a49d8 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -175,7 +175,14 @@ Rectangle { displayNameInput.text = getAvatarsData.displayName; currentAvatarSettings = getAvatarsData.currentAvatarSettings; - updateCurrentAvatarInBookmarks(currentAvatar); + var bookmarkAvatarIndex = allAvatars.findAvatarIndexByValue(currentAvatar); + if (bookmarkAvatarIndex === -1) { + currentAvatar.name = ''; + } else { + currentAvatar.name = allAvatars.get(bookmarkAvatarIndex).name; + allAvatars.move(bookmarkAvatarIndex, 0, 1); + } + view.setPage(0); } else if (message.method === 'updateAvatarInBookmarks') { updateCurrentAvatarInBookmarks(currentAvatar); } else if (message.method === 'selectAvatarEntity') { From 0f6fdef9284c95bc8a68e031bca7796618ec519d Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 30 Aug 2018 15:06:05 -0700 Subject: [PATCH 163/744] Fixed compilation error (within #ifdef) --- libraries/fbx/src/OBJReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 501fb72130..c46a1e234c 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -273,7 +273,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { " diffuse texture:" << currentMaterial.diffuseTextureFilename << " specular texture:" << currentMaterial.specularTextureFilename << " emissive texture:" << currentMaterial.emissiveTextureFilename << - " bump texture:" << currentMaterial.bumpTextureFilename; + " bump texture:" << currentMaterial.bumpTextureFilename << " opacity texture:" << currentMaterial.opacityTextureFilename; #endif return; From cb1855b0d7163e7170bb62185d68db3b8a5a285a Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Wed, 29 Aug 2018 22:34:35 +0300 Subject: [PATCH 164/744] filter out left/right hands from joints list in avatarapp should fix: FB17884 - Wearable disappears when added to some joints FB17885 - Changing werable joint moves the model and detaches from avatar body --- interface/resources/qml/hifi/AvatarApp.qml | 2 +- .../qml/hifi/avatarapp/AdjustWearables.qml | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index d8eae311d8..0cbf0637f8 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -46,7 +46,7 @@ Rectangle { } } - property var jointNames; + property var jointNames: [] property var currentAvatarSettings; function fetchAvatarModelName(marketId, avatar) { diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index e80dab60c2..b63720408e 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -25,7 +25,17 @@ Rectangle { modified = false; } - property var jointNames; + property var jointNames: [] + onJointNamesChanged: { + jointsModel.clear(); + for (var i = 0; i < jointNames.length; ++i) { + var jointName = jointNames[i]; + if(jointName !== 'LeftHand' && jointName !== 'RightHand') { + jointsModel.append({'text' : jointName, 'jointIndex' : i}); + } + } + } + property string avatarName: '' property var wearablesModel; @@ -268,20 +278,30 @@ Rectangle { anchors.right: parent.right enabled: getCurrentWearable() !== null && !isSoft.checked comboBox.displayText: isSoft.checked ? 'Hips' : comboBox.currentText + comboBox.textRole: "text" - model: jointNames + model: ListModel { + id: jointsModel + } property bool notify: false function set(jointIndex) { notify = false; - currentIndex = jointIndex; + for(var i = 0; i < jointsModel.count; ++i) { + if(jointsModel.get(i).jointIndex === jointIndex) { + currentIndex = i; + break; + } + } notify = true; } function notifyJointChanged() { modified = true; + var jointIndex = jointsModel.get(jointsCombobox.currentIndex).jointIndex; + var properties = { - parentJointIndex: currentIndex, + parentJointIndex: jointIndex, localPosition: { x: positionVector.xvalue, y: positionVector.yvalue, @@ -294,7 +314,7 @@ Rectangle { } }; - wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties); + wearableUpdated(getCurrentWearable().id, jointIndex, properties); } onCurrentIndexChanged: { From 07739453c586eab4c42ed16e42e92f35e07db367 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Thu, 30 Aug 2018 07:44:55 +0300 Subject: [PATCH 165/744] coding standards --- interface/resources/qml/hifi/avatarapp/AdjustWearables.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index b63720408e..5bf867a331 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -30,7 +30,7 @@ Rectangle { jointsModel.clear(); for (var i = 0; i < jointNames.length; ++i) { var jointName = jointNames[i]; - if(jointName !== 'LeftHand' && jointName !== 'RightHand') { + if (jointName !== 'LeftHand' && jointName !== 'RightHand') { jointsModel.append({'text' : jointName, 'jointIndex' : i}); } } @@ -287,8 +287,8 @@ Rectangle { function set(jointIndex) { notify = false; - for(var i = 0; i < jointsModel.count; ++i) { - if(jointsModel.get(i).jointIndex === jointIndex) { + for (var i = 0; i < jointsModel.count; ++i) { + if (jointsModel.get(i).jointIndex === jointIndex) { currentIndex = i; break; } From 5f08ed5027a8904242a47b17e994ab4f309e3930 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 30 Aug 2018 15:13:52 -0700 Subject: [PATCH 166/744] misc perf improvements --- .../src/avatars/AvatarMixerSlave.cpp | 6 +- interface/src/avatar/AvatarManager.cpp | 23 +++----- interface/src/avatar/OtherAvatar.cpp | 9 ++- .../src/controllers/UserInputMapper.cpp | 6 +- .../src/EntityTreeRenderer.cpp | 9 ++- .../graphics/src/graphics/BufferViewHelpers.h | 5 +- libraries/shared/src/AABox.cpp | 56 ++++--------------- libraries/shared/src/AABox.h | 17 +++++- libraries/shared/src/GLMHelpers.h | 8 +++ libraries/shared/src/PrioritySortUtil.h | 15 +++-- tests/shared/src/AACubeTests.cpp | 2 +- tests/shared/src/GLMHelpersTests.cpp | 36 ++++++++++++ tests/shared/src/GLMHelpersTests.h | 1 + 13 files changed, 108 insertions(+), 85 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index f347ff1f10..59c6db5dc4 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -429,9 +429,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); - while (!sortedAvatars.empty()) { - const auto avatarData = sortedAvatars.top().getAvatar(); - sortedAvatars.pop(); + const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); + for (const auto& sortedAvatar : sortedAvatarVector) { + const auto& avatarData = sortedAvatar.getAvatar(); remainingAvatars--; auto otherNode = avatarDataToNodes[avatarData]; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 9a7d8ef0c8..af9d9ad6b1 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -206,6 +206,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } ++itr; } + const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); // process in sorted order uint64_t startTime = usecTimestampNow(); @@ -216,8 +217,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { render::Transaction renderTransaction; workload::Transaction workloadTransaction; - while (!sortedAvatars.empty()) { - const SortableAvatar& sortData = sortedAvatars.top(); + for (auto it = sortedAvatarVector.begin(); it != sortedAvatarVector.end(); ++it) { + const SortableAvatar& sortData = *it; const auto avatar = std::static_pointer_cast(sortData.getAvatar()); // TODO: to help us scale to more avatars it would be nice to not have to poll orb state here @@ -231,7 +232,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); if (ignoring) { - sortedAvatars.pop(); continue; } @@ -260,26 +260,17 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // --> some avatar velocity measurements may be a little off // no time to simulate, but we take the time to count how many were tragically missed - bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD; - if (!inView) { - break; - } - if (inView && avatar->hasNewJointData()) { - numAVatarsNotUpdated++; - } - sortedAvatars.pop(); - while (inView && !sortedAvatars.empty()) { - const SortableAvatar& newSortData = sortedAvatars.top(); + while (it != sortedAvatarVector.end()) { + const SortableAvatar& newSortData = *it; const auto newAvatar = std::static_pointer_cast(newSortData.getAvatar()); - inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; + bool inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; if (inView && newAvatar->hasNewJointData()) { numAVatarsNotUpdated++; } - sortedAvatars.pop(); + ++it; } break; } - sortedAvatars.pop(); } if (_shouldRender) { diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 29ad5aed91..a0fa496c4c 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -29,20 +29,23 @@ OtherAvatar::~OtherAvatar() { } void OtherAvatar::removeOrb() { - if (qApp->getOverlays().isAddedOverlay(_otherAvatarOrbMeshPlaceholderID)) { + if (!_otherAvatarOrbMeshPlaceholderID.isNull()) { qApp->getOverlays().deleteOverlay(_otherAvatarOrbMeshPlaceholderID); + _otherAvatarOrbMeshPlaceholderID = UNKNOWN_OVERLAY_ID; } } void OtherAvatar::updateOrbPosition() { if (_otherAvatarOrbMeshPlaceholder != nullptr) { _otherAvatarOrbMeshPlaceholder->setWorldPosition(getHead()->getPosition()); + if (_otherAvatarOrbMeshPlaceholderID.isNull()) { + _otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder); + } } } void OtherAvatar::createOrb() { - if (_otherAvatarOrbMeshPlaceholderID == UNKNOWN_OVERLAY_ID || - !qApp->getOverlays().isAddedOverlay(_otherAvatarOrbMeshPlaceholderID)) { + if (_otherAvatarOrbMeshPlaceholderID.isNull()) { _otherAvatarOrbMeshPlaceholder = std::make_shared(); _otherAvatarOrbMeshPlaceholder->setAlpha(1.0f); _otherAvatarOrbMeshPlaceholder->setColor({ 0xFF, 0x00, 0xFF }); diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 371deec7d5..307064c073 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -527,8 +527,8 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { } // If the source hasn't been written yet, defer processing of this route - auto source = route->source; - auto sourceInput = source->getInput(); + auto& source = route->source; + auto& sourceInput = source->getInput(); if (sourceInput.device == STANDARD_DEVICE && !force && source->writeable()) { if (debugRoutes && route->debug) { qCDebug(controllers) << "Source not yet written, deferring"; @@ -559,7 +559,7 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { return true; } - auto destination = route->destination; + auto& destination = route->destination; // THis could happen if the route destination failed to create // FIXME: Maybe do not create the route if the destination failed and avoid this case ? if (!destination) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c3c4095251..a363093083 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -405,11 +405,14 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // process the sorted renderables size_t numSorted = sortedRenderables.size(); - while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const auto renderable = sortedRenderables.top().getRenderer(); + const auto& sortedRenderablesVector = sortedRenderables.getSortedVector(); + for (const auto& sortedRenderable : sortedRenderablesVector) { + if (usecTimestampNow() > expiry) { + break; + } + const auto& renderable = sortedRenderable.getRenderer(); renderable->updateInScene(scene, transaction); _renderablesToUpdate.erase(renderable->getEntity()->getID()); - sortedRenderables.pop(); } // compute average per-renderable update cost diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.h b/libraries/graphics/src/graphics/BufferViewHelpers.h index a9707c3128..7c37c75163 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.h +++ b/libraries/graphics/src/graphics/BufferViewHelpers.h @@ -13,6 +13,7 @@ #include #include "GpuHelpers.h" +#include "GLMHelpers.h" namespace graphics { class Mesh; @@ -55,8 +56,8 @@ namespace buffer_helpers { tangent = glm::clamp(tangent, -1.0f, 1.0f); normal *= 511.0f; tangent *= 511.0f; - normal = glm::round(normal); - tangent = glm::round(tangent); + normal = fastRoundf(normal); + tangent = fastRoundf(tangent); glm::detail::i10i10i10i2 normalStruct; glm::detail::i10i10i10i2 tangentStruct; diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index b4384c494f..e537c3e56a 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -79,33 +79,23 @@ void AABox::setBox(const glm::vec3& corner, const glm::vec3& scale) { glm::vec3 AABox::getFarthestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; - if (normal.x > 0.0f) { - result.x += _scale.x; - } - if (normal.y > 0.0f) { - result.y += _scale.y; - } - if (normal.z > 0.0f) { - result.z += _scale.z; - } + float blend = (float)(normal.x > 0.0f); + result.x += blend * _scale.x + (1.0f - blend) * 0.0f; + blend = (float)(normal.y > 0.0f); + result.y += blend * _scale.y + (1.0f - blend) * 0.0f; + blend = (float)(normal.z > 0.0f); + result.z += blend * _scale.z + (1.0f - blend) * 0.0f; return result; } glm::vec3 AABox::getNearestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; - - if (normal.x < 0.0f) { - result.x += _scale.x; - } - - if (normal.y < 0.0f) { - result.y += _scale.y; - } - - if (normal.z < 0.0f) { - result.z += _scale.z; - } - + float blend = (float)(normal.x < 0.0f); + result.x += blend * _scale.x + (1.0f - blend) * 0.0f; + blend = (float)(normal.y < 0.0f); + result.y += blend * _scale.y + (1.0f - blend) * 0.0f; + blend = (float)(normal.z < 0.0f); + result.z += blend * _scale.z + (1.0f - blend) * 0.0f; return result; } @@ -459,28 +449,6 @@ AABox AABox::clamp(float min, float max) const { return AABox(clampedCorner, clampedScale); } -AABox& AABox::operator += (const glm::vec3& point) { - - if (isInvalid()) { - _corner = glm::min(_corner, point); - } else { - glm::vec3 maximum(_corner + _scale); - _corner = glm::min(_corner, point); - maximum = glm::max(maximum, point); - _scale = maximum - _corner; - } - - return (*this); -} - -AABox& AABox::operator += (const AABox& box) { - if (!box.isInvalid()) { - (*this) += box._corner; - (*this) += box.calcTopFarLeft(); - } - return (*this); -} - void AABox::embiggen(float scale) { _corner += scale * (-0.5f * _scale); _scale *= scale; diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index daad01d7c7..a56615c40e 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -85,8 +85,21 @@ public: AABox clamp(const glm::vec3& min, const glm::vec3& max) const; AABox clamp(float min, float max) const; - AABox& operator += (const glm::vec3& point); - AABox& operator += (const AABox& box); + inline AABox& operator+=(const glm::vec3& point) { + float blend = (float)isInvalid(); + glm::vec3 maximumScale(glm::max(_scale, point - _corner)); + _corner = glm::min(_corner, point); + _scale = blend * _scale + (1.0f - blend) * maximumScale; + return (*this); + } + + inline AABox& operator+=(const AABox& box) { + if (!box.isInvalid()) { + (*this) += box._corner; + (*this) += box.calcTopFarLeft(); + } + return (*this); + } // Translate the AABox just moving the corner void translate(const glm::vec3& translation) { _corner += translation; } diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 7e6ef4cb28..619f8172d5 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -316,4 +316,12 @@ inline void glm_mat4u_mul(const glm::mat4& m1, const glm::mat4& m2, glm::mat4& r #endif } +inline glm::vec3 fastRoundf(const glm::vec3& vec) { +#if GLM_ARCH & GLM_ARCH_SSE2_BIT + return glm::vec3(_mm_cvt_ss2si(_mm_set_ss(vec.x)), _mm_cvt_ss2si(_mm_set_ss(vec.y)), _mm_cvt_ss2si(_mm_set_ss(vec.z))); +#else + return glm::round(vec); +#endif +} + #endif // hifi_GLMHelpers_h diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 34ec074d45..e0137b3d8c 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -12,7 +12,6 @@ #define hifi_PrioritySortUtil_h #include -#include #include "NumericalConstants.h" #include "shared/ConicalViewFrustum.h" @@ -75,7 +74,6 @@ namespace PrioritySortUtil { void setPriority(float priority) { _priority = priority; } float getPriority() const { return _priority; } - bool operator<(const Sortable& other) const { return _priority < other._priority; } private: float _priority { 0.0f }; }; @@ -97,14 +95,15 @@ namespace PrioritySortUtil { _ageWeight = ageWeight; } - size_t size() const { return _queue.size(); } + size_t size() const { return _vector.size(); } void push(T thing) { thing.setPriority(computePriority(thing)); - _queue.push(thing); + _vector.push_back(thing); + } + const std::vector& getSortedVector() { + std::sort(_vector.begin(), _vector.end(), [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); }); + return _vector; } - const T& top() const { return _queue.top(); } - void pop() { return _queue.pop(); } - bool empty() const { return _queue.empty(); } private: @@ -153,7 +152,7 @@ namespace PrioritySortUtil { } ConicalViewFrustums _views; - std::priority_queue _queue; + std::vector _vector; float _angularWeight { DEFAULT_ANGULAR_COEF }; float _centerWeight { DEFAULT_CENTER_COEF }; float _ageWeight { DEFAULT_AGE_COEF }; diff --git a/tests/shared/src/AACubeTests.cpp b/tests/shared/src/AACubeTests.cpp index 95a4d7f9f0..c3c8e3e6f7 100644 --- a/tests/shared/src/AACubeTests.cpp +++ b/tests/shared/src/AACubeTests.cpp @@ -173,7 +173,7 @@ void AACubeTests::rayVsParabolaPerformance() { glm::vec3 normal; auto start = std::chrono::high_resolution_clock::now(); for (auto& cube : cubes) { - if (cube.findRayIntersection(origin, direction, distance, face, normal)) { + if (cube.findRayIntersection(origin, direction, 1.0f / direction, distance, face, normal)) { numRayHits++; } } diff --git a/tests/shared/src/GLMHelpersTests.cpp b/tests/shared/src/GLMHelpersTests.cpp index 93c4735a6d..669bbb8e43 100644 --- a/tests/shared/src/GLMHelpersTests.cpp +++ b/tests/shared/src/GLMHelpersTests.cpp @@ -214,3 +214,39 @@ void GLMHelpersTests::testGenerateBasisVectors() { QCOMPARE_WITH_ABS_ERROR(w, z, EPSILON); } } + +void GLMHelpersTests::roundPerf() { + const int NUM_VECS = 1000000; + const float MAX_VEC = 500.0f; + std::vector vecs; + vecs.reserve(NUM_VECS); + for (int i = 0; i < NUM_VECS; i++) { + vecs.emplace_back(randFloatInRange(-MAX_VEC, MAX_VEC), randFloatInRange(-MAX_VEC, MAX_VEC), randFloatInRange(-MAX_VEC, MAX_VEC)); + } + std::vector vecs2 = vecs; + std::vector originalVecs = vecs; + + auto start = std::chrono::high_resolution_clock::now(); + for (auto& vec : vecs) { + vec = glm::round(vec); + } + + auto glmTime = std::chrono::high_resolution_clock::now() - start; + start = std::chrono::high_resolution_clock::now(); + for (auto& vec : vecs2) { + vec = fastRoundf(vec); + } + auto manualTime = std::chrono::high_resolution_clock::now() - start; + + bool identical = true; + for (int i = 0; i < vecs.size(); i++) { + identical &= vecs[i] == vecs2[i]; + if (vecs[i] != vecs2[i]) { + qDebug() << "glm: " << vecs[i].x << vecs[i].y << vecs[i].z << ", manual: " << vecs2[i].x << vecs2[i].y << vecs2[i].z; + qDebug() << "original: " << originalVecs[i].x << originalVecs[i].y << originalVecs[i].z; + break; + } + } + + qDebug() << "ratio: " << (float)glmTime.count() / (float)manualTime.count() << ", identical: " << identical; +} \ No newline at end of file diff --git a/tests/shared/src/GLMHelpersTests.h b/tests/shared/src/GLMHelpersTests.h index 030f2d477f..4d9bd0bb60 100644 --- a/tests/shared/src/GLMHelpersTests.h +++ b/tests/shared/src/GLMHelpersTests.h @@ -22,6 +22,7 @@ private slots: void testSixByteOrientationCompression(); void testSimd(); void testGenerateBasisVectors(); + void roundPerf(); }; float getErrorDifference(const float& a, const float& b); From 33db1394e6dde359fda68a131354f7a56066c42e Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 30 Aug 2018 17:12:42 -0700 Subject: [PATCH 167/744] Use system_clock in usecTimestampNow for cross-platform goodness --- libraries/shared/src/SharedUtil.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index a36bdb061e..6c79a23c20 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -134,7 +134,8 @@ void usecTimestampNowForceClockSkew(qint64 clockSkew) { quint64 usecTimestampNow(bool wantDebug) { using namespace std::chrono; - return duration_cast(high_resolution_clock::now().time_since_epoch()).count(); + static const auto unixEpoch = system_clock::from_time_t(0); + return duration_cast(system_clock::now() - unixEpoch).count(); #if 0 std::call_once(usecTimestampNowIsInitialized, [&] { TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec From 07b24b47688763d1cab3979264713837ee54b38c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 30 Aug 2018 17:22:27 -0700 Subject: [PATCH 168/744] send kill packet to ignored node for ignorer --- assignment-client/src/avatars/AvatarMixer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index edbba20dc7..0289a8aa3f 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -654,6 +654,12 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer if (addToIgnore) { senderNode->addIgnoredNode(ignoredUUID); + + // send a reliable kill packet to remove the sending avatar for the ignored avatar + auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); + killPacket->write(senderNode->getUUID().toRfc4122()); + killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected); + nodeList->sendPacket(std::move(killPacket), *ignoredNode); } else { senderNode->removeIgnoredNode(ignoredUUID); } From 5f43f5b27626d8120b67134bca2decb3d91db58a Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 30 Aug 2018 17:40:13 -0700 Subject: [PATCH 169/744] adding different method for handling redirect --- interface/src/Application.cpp | 32 ++++----------------- interface/src/Application.h | 2 +- libraries/networking/src/AddressManager.cpp | 4 ++- libraries/networking/src/DomainHandler.cpp | 14 +++++++++ libraries/networking/src/DomainHandler.h | 6 ++++ 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 348d1d5690..1041ab64ca 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1197,11 +1197,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo getOverlays().deleteOverlay(getTabletHomeButtonID()); getOverlays().deleteOverlay(getTabletFrameID()); }); -#if defined(Q_OS_ANDROID) connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); -#else - connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRedirect); -#endif + + &domainHandler.setErrorDomainURL(QUrl(REDIRECT_HIFI_ADDRESS)); // We could clear ATP assets only when changing domains, but it's possible that the domain you are connected // to has gone down and switched to a new content set, so when you reconnect the cached ATP assets will no longer be valid. @@ -2350,31 +2348,11 @@ void Application::domainConnectionRefused(const QString& reasonMessage, int reas } } -void Application::domainConnectionRedirect(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) { - DomainHandler::ConnectionRefusedReason reasonCode = static_cast(reasonCodeInt); +void Application::domainConnectionRedirect() { auto addressManager = DependencyManager::get(); - if (reasonCode == DomainHandler::ConnectionRefusedReason::TooManyUsers && !extraInfo.isEmpty()) { - addressManager->handleLookupString(extraInfo); - return; - } - - switch (reasonCode) { - case DomainHandler::ConnectionRefusedReason::ProtocolMismatch: - case DomainHandler::ConnectionRefusedReason::TooManyUsers: - case DomainHandler::ConnectionRefusedReason::Unknown: { - QString message = "Unable to connect to the location you are visiting.\n"; - message += reasonMessage; - //OffscreenUi::asyncWarning("", message); - addressManager->handleLookupString(REDIRECT_HIFI_ADDRESS); - getMyAvatar()->setWorldVelocity(glm::vec3(0.0f)); - // in (w, x, y, z) component-structure for the constructor - break; - } - default: - // nothing to do. - break; - } + addressManager->handleLookupString(REDIRECT_HIFI_ADDRESS); + getMyAvatar()->setWorldVelocity(glm::vec3(0.0f)); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 61548f203b..e38b5a3919 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -474,7 +474,7 @@ private slots: void updateDisplayMode(); void setDisplayPlugin(DisplayPluginPointer newPlugin); void domainConnectionRefused(const QString& reasonMessage, int reason, const QString& extraInfo); - void domainConnectionRedirect(const QString& reasonMessage, int reason, const QString& extraInfo); + void domainConnectionRedirect(); void addAssetToWorldCheckModelSize(); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 732a2d7963..01db8dfd79 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -319,7 +319,9 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // lookupUrl.scheme() == URL_SCHEME_HTTPS || _previousLookup.clear(); _shareablePlaceName.clear(); - setDomainInfo(lookupUrl, trigger); + if (lookupUrl.toString() != REDIRECT_HIFI_ADDRESS) { + setDomainInfo(lookupUrl, trigger); + } emit lookupResultsFinished(); QString path = DOMAIN_SPAWNING_POINT; diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 39c8b5b1a1..827232129f 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -128,6 +128,10 @@ void DomainHandler::hardReset() { _pendingPath.clear(); } +void DomainHandler::setErrorDomainURL(const QUrl& url) { + return; +} + void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) { if (_sockAddr != sockAddr) { // we should reset on a sockAddr change @@ -451,7 +455,17 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index a428110db6..473cac9133 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -50,6 +50,9 @@ public: QString getHostname() const { return _domainURL.host(); } + QUrl getErrorDomainURL(){ return _errorDomainURL; } + void setErrorDomainURL(const QUrl& url); + const QHostAddress& getIP() const { return _sockAddr.getAddress(); } void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); } @@ -179,6 +182,7 @@ signals: void settingsReceiveFail(); void domainConnectionRefused(QString reasonMessage, int reason, const QString& extraInfo); + void redirectToErrorDomainURL(); void limitOfSilentDomainCheckInsReached(); @@ -190,6 +194,7 @@ private: QUuid _uuid; Node::LocalID _localID; QUrl _domainURL; + QUrl _errorDomainURL; HifiSockAddr _sockAddr; QUuid _assignmentUUID; QUuid _connectionToken; @@ -198,6 +203,7 @@ private: HifiSockAddr _iceServerSockAddr; NetworkPeer _icePeer; bool _isConnected { false }; + bool _isInErrorState { false }; QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; From 194903692ed5b7cc13e6b939a2bed89ea8c7ee26 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Thu, 30 Aug 2018 18:09:11 -0700 Subject: [PATCH 170/744] clening up the code in LODmanager and the associated js/qml --- interface/src/LODManager.cpp | 498 ++++++++++----------- interface/src/LODManager.h | 157 +++---- interface/src/ui/PreferencesDialog.cpp | 48 -- libraries/render/src/render/Args.h | 10 +- libraries/render/src/render/CullTask.cpp | 2 +- scripts/developer/utilities/render/lod.qml | 13 +- 6 files changed, 317 insertions(+), 411 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 925d48f9cd..738c3dd9fb 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -19,27 +19,15 @@ #include "ui/DialogsManager.h" #include "InterfaceLogging.h" +const float LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS = LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS; +const float LODManager::DEFAULT_HMD_LOD_DOWN_FPS = LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS; -Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS); -Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS); +Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS); +Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", LODManager::DEFAULT_HMD_LOD_DOWN_FPS); LODManager::LODManager() { } -float LODManager::getLODDecreaseFPS() const { - if (qApp->isHMDMode()) { - return getHMDLODDecreaseFPS(); - } - return getDesktopLODDecreaseFPS(); -} - -float LODManager::getLODIncreaseFPS() const { - if (qApp->isHMDMode()) { - return getHMDLODIncreaseFPS(); - } - return getDesktopLODIncreaseFPS(); -} - // We use a "time-weighted running average" of the maxRenderTime and compare it against min/max thresholds // to determine if we should adjust the level of detail (LOD). // @@ -55,8 +43,12 @@ const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.08f; // sec // multiples of the running average timescale: const uint64_t LOD_AUTO_ADJUST_PERIOD = 4 * (uint64_t)(LOD_ADJUST_RUNNING_AVG_TIMESCALE * (float)USECS_PER_MSEC); // usec -const float LOD_AUTO_ADJUST_DECREMENT_FACTOR = 0.8f; -const float LOD_AUTO_ADJUST_INCREMENT_FACTOR = 1.2f; +// batchTIme is always contained in presentTime. +// We favor using batchTime instead of presentTime as a representative value for rendering duration (on present thread) +// if batchTime + cushionTime < presentTime. +// since we are shooting for fps around 60, 90Hz, the ideal frames are around 10ms +// so we are picking a cushion time of 3ms +const float LOD_BATCH_TO_PRESENT_CUSHION_TIME = 10.0f; // msec void LODManager::setRenderTimes(float presentTime, float engineRunTime, float batchTime, float gpuTime) { _presentTime = presentTime; @@ -66,6 +58,7 @@ void LODManager::setRenderTimes(float presentTime, float engineRunTime, float ba } void LODManager::autoAdjustLOD(float realTimeDelta) { + // The "render time" is the worse of: // - engineRunTime: Time spent in the render thread in the engine producing the gpu::Frame N // - batchTime: Time spent in the present thread processing the batches of gpu::Frame N+1 @@ -73,50 +66,42 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { // - gpuTime: Time spent in the GPU executing the gpu::Frame N + 2 // But Present time is in reality synched with the monitor/display refresh rate, it s always longer than batchTime. - // So if batchTime is fast enough relative to PResent Time we are using it, otherwise we are using presentTime. got it ? - auto presentTime = (_presentTime - _batchTime > 3.0f ? _batchTime + 3.0f : _presentTime); + // So if batchTime is fast enough relative to presentTime we are using it, otherwise we are using presentTime. got it ? + auto presentTime = (_presentTime > _batchTime + LOD_BATCH_TO_PRESENT_CUSHION_TIME ? _batchTime + LOD_BATCH_TO_PRESENT_CUSHION_TIME : _presentTime); float maxRenderTime = glm::max(glm::max(presentTime, _engineRunTime), _gpuTime); // compute time-weighted running average maxRenderTime // Note: we MUST clamp the blend to 1.0 for stability - float blend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; - _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * maxRenderTime; // msec + float nowBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; + _nowRenderTime = (1.0f - nowBlend) * _nowRenderTime + nowBlend * maxRenderTime; // msec float smoothBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) ? realTimeDelta / (LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) : 1.0f; _smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec - - // _avgRenderTime = maxRenderTime; - if (!_automaticLODAdjust || _avgRenderTime == 0.0f) { + if (!_automaticLODAdjust || _nowRenderTime == 0.0f) { // early exit return; } - float oldOctreeSizeScale = _octreeSizeScale; - float oldSolidAngle = getLODAngleDeg(); + // Previous values for output + float oldOctreeSizeScale = getOctreeSizeScale(); + float oldLODAngle = getLODAngleDeg(); - float targetFPS = 0.5 * (getLODDecreaseFPS() + getLODIncreaseFPS()); - float targetPeriod = 1.0f / targetFPS; - - float currentFPS = (float)MSECS_PER_SECOND / _avgRenderTime; + // Target fps and current fps based on latest measurments + float targetFPS = getLODTargetFPS(); + float currentNowFPS = (float)MSECS_PER_SECOND / _nowRenderTime; float currentSmoothFPS = (float)MSECS_PER_SECOND / _smoothRenderTime; // Compute the Variance of the FPS signal (FPS - smouthFPS)^2 // Also scale it by a percentage for fine tuning (default is 100%) - float currentVarianceFPS = (currentSmoothFPS - currentFPS); + float currentVarianceFPS = (currentSmoothFPS - currentNowFPS); currentVarianceFPS *= currentVarianceFPS; currentVarianceFPS *= _pidCoefs.w; - auto dt = realTimeDelta; - - auto previous_error = _pidHistory.x; - auto previous_integral = _pidHistory.y; - - auto smoothError = (targetFPS - currentSmoothFPS); - - auto fpsError = smoothError; - - auto errorSquare = smoothError * smoothError; + // evaluate current error between the current smoothFPS and target FPS + // and the sqaure of the error to compare against the Variance + auto currentErrorFPS = (targetFPS - currentSmoothFPS); + auto currentErrorFPSSquare = currentErrorFPS * currentErrorFPS; // Define a noiseCoef that is trying to adjust the error to the FPS target value based on its strength // relative to the current Variance of the FPS signal. @@ -124,41 +109,48 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { // if its within 2x the variance scale the control // and full control if error is bigger than 2x variance auto noiseCoef = 1.0f; - if (errorSquare < currentVarianceFPS) { + if (currentErrorFPSSquare < currentVarianceFPS) { noiseCoef = 0.0f; - } else if (errorSquare < 2.0f * currentVarianceFPS) { - noiseCoef = (errorSquare - currentVarianceFPS) / currentVarianceFPS; + } else if (currentErrorFPSSquare < 2.0f * currentVarianceFPS) { + noiseCoef = (currentErrorFPSSquare - currentVarianceFPS) / currentVarianceFPS; } // The final normalized error is the the error to the FPS target, weighted by the noiseCoef, then normailzed by the target FPS. // it s also clamped in the [-1, 1] range - auto error = noiseCoef * smoothError / targetFPS; + auto error = noiseCoef * currentErrorFPS / targetFPS; error = glm::clamp(error, -1.0f, 1.0f); + // Now we are getting into the P.I.D. controler code + // retreive the history of pid error and integral + auto previous_error = _pidHistory.x; + auto previous_integral = _pidHistory.y; + + // The dt used for temporal values of the controller is the current realTimedelta + // clamped to a reasonable granularity to make sure we are not over reacting + auto dt = std::min(realTimeDelta, LOD_ADJUST_RUNNING_AVG_TIMESCALE); + + // Compute the current integral and clamp to avoid accumulation auto integral = previous_integral + error * dt; glm::clamp(integral, -1.0f, 1.0f); + // Compute derivative auto derivative = (error - previous_error) / dt; + // remember history _pidHistory.x = error; _pidHistory.y = integral; _pidHistory.z = derivative; - auto Kp = _pidCoefs.x; - auto Ki = _pidCoefs.y; - auto Kd = _pidCoefs.z; - - _pidOutputs.x = Kp * error; - _pidOutputs.y = Ki * integral; - _pidOutputs.z = Kd * derivative; + // Compute the output of the PID and record intermediate results for tuning + _pidOutputs.x = _pidCoefs.x * error; // Kp * error + _pidOutputs.y = _pidCoefs.y * integral; // Ki * integral + _pidOutputs.z = _pidCoefs.z * derivative; // Kd * derivative auto output = _pidOutputs.x + _pidOutputs.y + _pidOutputs.z; - _pidOutputs.w = output; - auto newSolidAngle = oldSolidAngle + output; - - setLODAngleDeg(newSolidAngle); + // And now add the output of the controller to the LODAngle where we will guarantee it is in the proper range + setLODAngleDeg(oldLODAngle + output); if (oldOctreeSizeScale != _octreeSizeScale) { auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); @@ -168,207 +160,6 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { } } -void LODManager::resetLODAdjust() { - _decreaseFPSExpiry = _increaseFPSExpiry = usecTimestampNow() + LOD_AUTO_ADJUST_PERIOD; -} - -void LODManager::setAutomaticLODAdjust(bool value) { - _automaticLODAdjust = value; - emit autoLODChanged(); -} - -float LODManager::getLODLevel() const { - // simpleLOD is a linearized and normalized number that represents how much LOD is being applied. - // It ranges from: - // 1.0 = normal (max) level of detail - // 0.0 = min level of detail - // In other words: as LOD "drops" the value of simpleLOD will also "drop", and it cannot go lower than 0.0. - const float LOG_MIN_LOD_RATIO = logf(ADJUST_LOD_MIN_SIZE_SCALE / ADJUST_LOD_MAX_SIZE_SCALE); - float power = logf(_octreeSizeScale / ADJUST_LOD_MAX_SIZE_SCALE); - float simpleLOD = (LOG_MIN_LOD_RATIO - power) / LOG_MIN_LOD_RATIO; - return simpleLOD; -} - -void LODManager::setLODLevel(float level) { - float simpleLOD = level; - if (!_automaticLODAdjust) { - const float LOG_MIN_LOD_RATIO = logf(ADJUST_LOD_MIN_SIZE_SCALE / ADJUST_LOD_MAX_SIZE_SCALE); - - float power = LOG_MIN_LOD_RATIO - (simpleLOD * LOG_MIN_LOD_RATIO); - float sizeScale = expf(power) * ADJUST_LOD_MAX_SIZE_SCALE; - setOctreeSizeScale(sizeScale); - } -} - -const float MIN_DECREASE_FPS = 0.5f; - -void LODManager::setDesktopLODDecreaseFPS(float fps) { - if (fps < MIN_DECREASE_FPS) { - // avoid divide by zero - fps = MIN_DECREASE_FPS; - } - _desktopMaxRenderTime = (float)MSECS_PER_SECOND / fps; -} - -float LODManager::getDesktopLODDecreaseFPS() const { - return (float)MSECS_PER_SECOND / _desktopMaxRenderTime; -} - -float LODManager::getDesktopLODIncreaseFPS() const { - return glm::min(((float)MSECS_PER_SECOND / _desktopMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_DESKTOP_FPS); -} - -void LODManager::setHMDLODDecreaseFPS(float fps) { - if (fps < MIN_DECREASE_FPS) { - // avoid divide by zero - fps = MIN_DECREASE_FPS; - } - _hmdMaxRenderTime = (float)MSECS_PER_SECOND / fps; -} - -float LODManager::getHMDLODDecreaseFPS() const { - return (float)MSECS_PER_SECOND / _hmdMaxRenderTime; -} - -float LODManager::getHMDLODIncreaseFPS() const { - return glm::min(((float)MSECS_PER_SECOND / _hmdMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_HMD_FPS); -} - -QString LODManager::getLODFeedbackText() { - // determine granularity feedback - int boundaryLevelAdjust = getBoundaryLevelAdjust(); - QString granularityFeedback; - switch (boundaryLevelAdjust) { - case 0: { - granularityFeedback = QString("."); - } break; - case 1: { - granularityFeedback = QString(" at half of standard granularity."); - } break; - case 2: { - granularityFeedback = QString(" at a third of standard granularity."); - } break; - default: { - granularityFeedback = QString(" at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1); - } break; - } - // distance feedback - float octreeSizeScale = getOctreeSizeScale(); - float relativeToDefault = octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE; - int relativeToTwentyTwenty = 20 / relativeToDefault; - - QString result; - if (relativeToDefault > 1.01f) { - result = QString("20:%1 or %2 times further than average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault,0,'f',2).arg(granularityFeedback); - } else if (relativeToDefault > 0.99f) { - result = QString("20:20 or the default distance for average vision%1").arg(granularityFeedback); - } else if (relativeToDefault > 0.01f) { - result = QString("20:%1 or %2 of default distance for average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault,0,'f',3).arg(granularityFeedback); - } else { - result = QString("%2 of default distance for average vision%3").arg(relativeToDefault,0,'f',3).arg(granularityFeedback); - } - return result; -} - -bool LODManager::shouldRender(const RenderArgs* args, const AABox& bounds) { - // FIXME - eventually we want to use the render accuracy as an indicator for the level of detail - // to use in rendering. - // float renderAccuracy = calculateRenderAccuracy(args->getViewFrustum().getPosition(), bounds, args->_sizeScale, args->_boundaryLevelAdjust); - // return (renderAccuracy > 0.0f); - - auto pos = args->getViewFrustum().getPosition() - bounds.calcCenter(); - auto dim = bounds.getDimensions(); - auto halfTanSq = 0.25f * glm::dot(dim, dim) / glm::dot(pos, pos); - return (halfTanSq >= args->_solidAngleHalfTanSq); - -}; - -void LODManager::setOctreeSizeScale(float sizeScale) { - _octreeSizeScale = sizeScale; -} - -void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) { - _boundaryLevelAdjust = boundaryLevelAdjust; -} - -void LODManager::loadSettings() { - setDesktopLODDecreaseFPS(desktopLODDecreaseFPS.get()); - setHMDLODDecreaseFPS(hmdLODDecreaseFPS.get()); -} - -void LODManager::saveSettings() { - desktopLODDecreaseFPS.set(getDesktopLODDecreaseFPS()); - hmdLODDecreaseFPS.set(getHMDLODDecreaseFPS()); -} - - -void LODManager::setSmoothScale(float t) { - _smoothScale = glm::max(1.0f, t); -} - -void LODManager::setWorldDetailQuality(float quality) { - - static const float MAX_DESKTOP_FPS = 60; - static const float MAX_HMD_FPS = 90; - static const float MIN_FPS = 10; - static const float LOW = 0.25f; - static const float MEDIUM = 0.5f; - static const float HIGH = 0.75f; - static const float THRASHING_DIFFERENCE = 10; - - bool isLowestValue = quality == LOW; - bool isHMDMode = qApp->isHMDMode(); - - float maxFPS = isHMDMode ? MAX_HMD_FPS : MAX_DESKTOP_FPS; - float desiredFPS = maxFPS /* - THRASHING_DIFFERENCE*/; - - if (!isLowestValue) { - float calculatedFPS = (maxFPS - (maxFPS * quality))/* - THRASHING_DIFFERENCE*/; - desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS; - } - - if (isHMDMode) { - setHMDLODDecreaseFPS(desiredFPS); - } - else { - setDesktopLODDecreaseFPS(desiredFPS); - } - - emit worldDetailQualityChanged(); -} - -float LODManager::getWorldDetailQuality() const { - - - static const float MAX_DESKTOP_FPS = 60; - static const float MAX_HMD_FPS = 90; - static const float MIN_FPS = 10; - static const float LOW = 0.25f; - static const float MEDIUM = 0.5f; - static const float HIGH = 0.75f; - - bool inHMD = qApp->isHMDMode(); - - float increaseFPS = 0; - if (inHMD) { - increaseFPS = getHMDLODDecreaseFPS(); - } else { - increaseFPS = getDesktopLODDecreaseFPS(); - } - float maxFPS = inHMD ? MAX_HMD_FPS : MAX_DESKTOP_FPS; - float percentage = 1.0 - increaseFPS / maxFPS; - - if (percentage <= LOW) { - return LOW; - } - else if (percentage <= MEDIUM) { - return MEDIUM; - } - - return HIGH; -} - - float LODManager::getLODAngleHalfTan() const { return getPerspectiveAccuracyAngleTan(_octreeSizeScale, _boundaryLevelAdjust); } @@ -386,6 +177,10 @@ void LODManager::setLODAngleDeg(float lodAngle) { setOctreeSizeScale(octreeSizeScale); } +void LODManager::setSmoothScale(float t) { + _smoothScale = glm::max(1.0f, t); +} + float LODManager::getPidKp() const { return _pidCoefs.x; } @@ -422,4 +217,189 @@ float LODManager::getPidOd() const { } float LODManager::getPidO() const { return _pidOutputs.w; -} \ No newline at end of file +} + +void LODManager::resetLODAdjust() { + // _decreaseFPSExpiry = _increaseFPSExpiry = usecTimestampNow() + LOD_AUTO_ADJUST_PERIOD; +} + +void LODManager::setAutomaticLODAdjust(bool value) { + _automaticLODAdjust = value; + emit autoLODChanged(); +} + +bool LODManager::shouldRender(const RenderArgs* args, const AABox& bounds) { + // To decide if the bound should be rendered or not at the specified Args->lodAngle, + // we need to compute the apparent angle of the bound from the frustum origin, + // and compare it against the lodAngle, if it is greater or equal we should render the content of that bound. + // we abstract the bound as a sphere centered on the bound center and of radius half diagonal of the bound. + + // Instead of comparing angles, we are comparing the tangent of the half angle which are more efficient to compute: + // we are comparing the square of the half tangent apparent angle for the bound against the LODAngle Half tangent square + // if smaller, the bound is too small and we should NOT render it, return true otherwise. + + // Tangent Adjacent side is eye to bound center vector length + auto pos = args->getViewFrustum().getPosition() - bounds.calcCenter(); + auto halfTanAdjacentSq = glm::dot(pos, pos); + + // Tangent Opposite side is the half length of the dimensions vector of the bound + auto dim = bounds.getDimensions(); + auto halfTanOppositeSq = 0.25f * glm::dot(dim, dim); + + // The test is: + // isVisible = halfTanSq >= lodHalfTanSq = (halfTanOppositeSq / halfTanAdjacentSq) >= lodHalfTanSq + // which we express as below to avoid division + // (halfTanOppositeSq) >= lodHalfTanSq * halfTanAdjacentSq + return (halfTanOppositeSq >= args->_lodAngleHalfTanSq * halfTanAdjacentSq); +}; + +void LODManager::setOctreeSizeScale(float sizeScale) { + _octreeSizeScale = sizeScale; +} + +void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) { + _boundaryLevelAdjust = boundaryLevelAdjust; +} + +QString LODManager::getLODFeedbackText() { + // determine granularity feedback + int boundaryLevelAdjust = getBoundaryLevelAdjust(); + QString granularityFeedback; + switch (boundaryLevelAdjust) { + case 0: { + granularityFeedback = QString("."); + } break; + case 1: { + granularityFeedback = QString(" at half of standard granularity."); + } break; + case 2: { + granularityFeedback = QString(" at a third of standard granularity."); + } break; + default: { + granularityFeedback = QString(" at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1); + } break; + } + // distance feedback + float octreeSizeScale = getOctreeSizeScale(); + float relativeToDefault = octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE; + int relativeToTwentyTwenty = 20 / relativeToDefault; + + QString result; + if (relativeToDefault > 1.01f) { + result = QString("20:%1 or %2 times further than average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault, 0, 'f', 2).arg(granularityFeedback); + } + else if (relativeToDefault > 0.99f) { + result = QString("20:20 or the default distance for average vision%1").arg(granularityFeedback); + } + else if (relativeToDefault > 0.01f) { + result = QString("20:%1 or %2 of default distance for average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault, 0, 'f', 3).arg(granularityFeedback); + } + else { + result = QString("%2 of default distance for average vision%3").arg(relativeToDefault, 0, 'f', 3).arg(granularityFeedback); + } + return result; +} + +void LODManager::loadSettings() { + setDesktopLODTargetFPS(desktopLODDecreaseFPS.get()); + setHMDLODTargetFPS(hmdLODDecreaseFPS.get()); +} + +void LODManager::saveSettings() { + desktopLODDecreaseFPS.set(getDesktopLODTargetFPS()); + hmdLODDecreaseFPS.set(getHMDLODTargetFPS()); +} + +const float MIN_DECREASE_FPS = 0.5f; + +void LODManager::setDesktopLODTargetFPS(float fps) { + if (fps < MIN_DECREASE_FPS) { + // avoid divide by zero + fps = MIN_DECREASE_FPS; + } + _desktopTargetFPS = fps; +} + +float LODManager::getDesktopLODTargetFPS() const { + return _desktopTargetFPS; +} + +void LODManager::setHMDLODTargetFPS(float fps) { + if (fps < MIN_DECREASE_FPS) { + // avoid divide by zero + fps = MIN_DECREASE_FPS; + } + _hmdTargetFPS = fps; +} + +float LODManager::getHMDLODTargetFPS() const { + return _hmdTargetFPS; +} + +float LODManager::getLODTargetFPS() const { + if (qApp->isHMDMode()) { + return getHMDLODTargetFPS(); + } + return getDesktopLODTargetFPS(); +} + +void LODManager::setWorldDetailQuality(float quality) { + static const float MIN_FPS = 10; + static const float LOW = 0.25f; + + bool isLowestValue = quality == LOW; + bool isHMDMode = qApp->isHMDMode(); + + float maxFPS = isHMDMode ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS; + float desiredFPS = maxFPS; + + if (!isLowestValue) { + float calculatedFPS = (maxFPS - (maxFPS * quality)); + desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS; + } + + if (isHMDMode) { + setHMDLODTargetFPS(desiredFPS); + } + else { + setDesktopLODTargetFPS(desiredFPS); + } + + emit worldDetailQualityChanged(); +} + +float LODManager::getWorldDetailQuality() const { + + static const float LOW = 0.25f; + static const float MEDIUM = 0.5f; + static const float HIGH = 0.75f; + + bool inHMD = qApp->isHMDMode(); + + float targetFPS = 0; + if (inHMD) { + targetFPS = getHMDLODTargetFPS(); + } else { + targetFPS = getDesktopLODTargetFPS(); + } + float maxFPS = inHMD ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS; + float percentage = 1.0 - targetFPS / maxFPS; + + if (percentage <= LOW) { + return LOW; + } + else if (percentage <= MEDIUM) { + return MEDIUM; + } + + return HIGH; +} + + +void LODManager::setLODQualityLevel(float quality) { + _lodQualityLevel; +} + +float LODManager::getLODQualityLevel() const { + return _lodQualityLevel; +} diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 6843744d16..0af745bcf4 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -19,20 +19,11 @@ #include #include -const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f; -const float DEFAULT_HMD_LOD_DOWN_FPS = 34.0f; -const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec -const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec -const float MAX_LIKELY_DESKTOP_FPS = 61.0f; // this is essentially, V-synch - 1 fps -const float MAX_LIKELY_HMD_FPS = 91.0f; // this is essentially, V-synch - 1 fps -const float INCREASE_LOD_GAP_FPS = 10.0f; // fps -// The default value DEFAULT_OCTREE_SIZE_SCALE means you can be 400 meters away from a 1 meter object in order to see it (which is ~20:20 vision). -const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE; -// This controls how low the auto-adjust LOD will go. We want a minimum vision of ~20:500 or 0.04 of default -// const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.04f; -// const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.02f; -const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.01f; +const float LOD_DEFAULT_QUALITY_LEVEL = 0.5f; // default quality level setting is Mid +const float LOD_MAX_LIKELY_DESKTOP_FPS = 60.0f; // this is essentially, V-synch fps +const float LOD_MAX_LIKELY_HMD_FPS = 90.0f; // this is essentially, V-synch fps +const float LOD_OFFSET_FPS = 5.0f; // offset of FPS to add for computing the target framerate class AABox; @@ -55,41 +46,42 @@ class AABox; class LODManager : public QObject, public Dependency { Q_OBJECT - SINGLETON_DEPENDENCY + SINGLETON_DEPENDENCY - Q_PROPERTY(float presentTime READ getPresentTime) - Q_PROPERTY(float engineRunTime READ getEngineRunTime) - Q_PROPERTY(float batchTime READ getBatchTime) - Q_PROPERTY(float gpuTime READ getGPUTime) + Q_PROPERTY(float worldDetailQuality READ getWorldDetailQuality WRITE setWorldDetailQuality NOTIFY worldDetailQualityChanged) - Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) - Q_PROPERTY(float fps READ getMaxTheoreticalFPS) + Q_PROPERTY(float lodQualityLevel READ getLODQualityLevel WRITE setLODQualityLevel NOTIFY lodQualityLevelChanged) - Q_PROPERTY(float smoothRenderTime READ getSmoothRenderTime) - Q_PROPERTY(float smoothFPS READ getSmoothFPS) - Q_PROPERTY(float smoothScale READ getSmoothScale WRITE setSmoothScale) + Q_PROPERTY(bool automaticLODAdjust READ getAutomaticLODAdjust WRITE setAutomaticLODAdjust NOTIFY autoLODChanged) - Q_PROPERTY(float lodLevel READ getLODLevel WRITE setLODLevel NOTIFY LODChanged) - Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) - Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) - Q_PROPERTY(bool automaticLODAdjust READ getAutomaticLODAdjust WRITE setAutomaticLODAdjust NOTIFY autoLODChanged) + Q_PROPERTY(float presentTime READ getPresentTime) + Q_PROPERTY(float engineRunTime READ getEngineRunTime) + Q_PROPERTY(float batchTime READ getBatchTime) + Q_PROPERTY(float gpuTime READ getGPUTime) - Q_PROPERTY(float worldDetailQuality READ getWorldDetailQuality WRITE setWorldDetailQuality NOTIFY worldDetailQualityChanged) + Q_PROPERTY(float nowRenderTime READ getNowRenderTime) + Q_PROPERTY(float nowRenderFPS READ getNowRenderFPS) - Q_PROPERTY(float lodAngleDeg READ getLODAngleDeg WRITE setLODAngleDeg) + Q_PROPERTY(float smoothScale READ getSmoothScale WRITE setSmoothScale) + Q_PROPERTY(float smoothRenderTime READ getSmoothRenderTime) + Q_PROPERTY(float smoothRenderFPS READ getSmoothRenderFPS) - Q_PROPERTY(float pidKp READ getPidKp WRITE setPidKp) - Q_PROPERTY(float pidKi READ getPidKi WRITE setPidKi) - Q_PROPERTY(float pidKd READ getPidKd WRITE setPidKd) - Q_PROPERTY(float pidKv READ getPidKv WRITE setPidKv) + Q_PROPERTY(float lodTargetFPS READ getLODTargetFPS) - Q_PROPERTY(float pidOp READ getPidOp) - Q_PROPERTY(float pidOi READ getPidOi) - Q_PROPERTY(float pidOd READ getPidOd) - Q_PROPERTY(float pidO READ getPidO) + Q_PROPERTY(float lodAngleDeg READ getLODAngleDeg WRITE setLODAngleDeg) + + Q_PROPERTY(float pidKp READ getPidKp WRITE setPidKp) + Q_PROPERTY(float pidKi READ getPidKi WRITE setPidKi) + Q_PROPERTY(float pidKd READ getPidKd WRITE setPidKd) + Q_PROPERTY(float pidKv READ getPidKv WRITE setPidKv) + + Q_PROPERTY(float pidOp READ getPidOp) + Q_PROPERTY(float pidOi READ getPidOi) + Q_PROPERTY(float pidOd READ getPidOd) + Q_PROPERTY(float pidO READ getPidO) public: - + /**jsdoc * @function LODManager.setAutomaticLODAdjust * @param {boolean} value @@ -103,42 +95,31 @@ public: Q_INVOKABLE bool getAutomaticLODAdjust() const { return _automaticLODAdjust; } /**jsdoc - * @function LODManager.setDesktopLODDecreaseFPS + * @function LODManager.setDesktopLODTargetFPS * @param {number} value */ - Q_INVOKABLE void setDesktopLODDecreaseFPS(float value); + Q_INVOKABLE void setDesktopLODTargetFPS(float value); /**jsdoc - * @function LODManager.getDesktopLODDecreaseFPS + * @function LODManager.getDesktopLODTargetFPS * @returns {number} */ - Q_INVOKABLE float getDesktopLODDecreaseFPS() const; + Q_INVOKABLE float getDesktopLODTargetFPS() const; /**jsdoc - * @function LODManager.getDesktopLODIncreaseFPS - * @returns {number} - */ - Q_INVOKABLE float getDesktopLODIncreaseFPS() const; - - /**jsdoc - * @function LODManager.setHMDLODDecreaseFPS + * @function LODManager.setHMDLODTargetFPS * @param {number} value */ - - Q_INVOKABLE void setHMDLODDecreaseFPS(float value); + + Q_INVOKABLE void setHMDLODTargetFPS(float value); /**jsdoc - * @function LODManager.getHMDLODDecreaseFPS + * @function LODManager.getHMDLODTargetFPS * @returns {number} */ - Q_INVOKABLE float getHMDLODDecreaseFPS() const; + Q_INVOKABLE float getHMDLODTargetFPS() const; - /**jsdoc - * @function LODManager.getHMDLODIncreaseFPS - * @returns {number} - */ - Q_INVOKABLE float getHMDLODIncreaseFPS() const; // User Tweakable LOD Items /**jsdoc @@ -172,16 +153,11 @@ public: Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } /**jsdoc - * @function LODManager.getLODDecreaseFPS - * @returns {number} - */ - Q_INVOKABLE float getLODDecreaseFPS() const; + * @function LODManager.getLODTargetFPS + * @returns {number} + */ + Q_INVOKABLE float getLODTargetFPS() const; - /**jsdoc - * @function LODManager.getLODIncreaseFPS - * @returns {number} - */ - Q_INVOKABLE float getLODIncreaseFPS() const; float getPresentTime() const { return _presentTime; } float getEngineRunTime() const { return _engineRunTime; } @@ -196,22 +172,21 @@ public: void saveSettings(); void resetLODAdjust(); + float getNowRenderTime() const { return _nowRenderTime; }; + float getNowRenderFPS() const { return (float)MSECS_PER_SECOND / _nowRenderTime; }; + void setSmoothScale(float t); float getSmoothScale() const { return _smoothScale; } - float getAverageRenderTime() const { return _avgRenderTime; }; - float getMaxTheoreticalFPS() const { return (float)MSECS_PER_SECOND / _avgRenderTime; }; - float getSmoothRenderTime() const { return _smoothRenderTime; }; - float getSmoothFPS() const { return (float)MSECS_PER_SECOND / _smoothRenderTime; }; - - - float getLODLevel() const; - void setLODLevel(float level); + float getSmoothRenderFPS() const { return (float)MSECS_PER_SECOND / _smoothRenderTime; }; void setWorldDetailQuality(float quality); float getWorldDetailQuality() const; + void setLODQualityLevel(float quality); + float getLODQualityLevel() const; + float getLODAngleDeg() const; void setLODAngleDeg(float lodAngle); float getLODAngleHalfTan() const; @@ -225,12 +200,15 @@ public: void setPidKi(float k); void setPidKd(float k); void setPidKv(float t); - + float getPidOp() const; float getPidOi() const; float getPidOd() const; float getPidO() const; + static const float DEFAULT_DESKTOP_LOD_DOWN_FPS; + static const float DEFAULT_HMD_LOD_DOWN_FPS; + signals: /**jsdoc @@ -245,24 +223,28 @@ signals: */ void LODDecreased(); - void LODChanged(); void autoLODChanged(); + void lodQualityLevelChanged(); void worldDetailQualityChanged(); private: LODManager(); bool _automaticLODAdjust = true; - float _presentTime { 0.0f }; // msec - float _engineRunTime { 0.0f }; // msec - float _batchTime{ 0.0f }; // msec - float _gpuTime { 0.0f }; // msec - float _smoothScale{ 8.0f }; - float _avgRenderTime { 0.0f }; // msec - float _smoothRenderTime{ 0.0f }; - float _desktopMaxRenderTime { DEFAULT_DESKTOP_MAX_RENDER_TIME }; - float _hmdMaxRenderTime { DEFAULT_HMD_MAX_RENDER_TIME }; + float _presentTime{ 0.0f }; // msec + float _engineRunTime{ 0.0f }; // msec + float _batchTime{ 0.0f }; // msec + float _gpuTime{ 0.0f }; // msec + + float _nowRenderTime{ 0.0f }; // msec + float _smoothScale{ 10.0f }; // smooth is evaluated over 10 times longer than now + float _smoothRenderTime{ 0.0f }; // msec + + float _lodQualityLevel{ LOD_DEFAULT_QUALITY_LEVEL }; + + float _desktopTargetFPS { LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS }; + float _hmdTargetFPS { LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS }; float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; @@ -270,9 +252,6 @@ private: glm::vec4 _pidCoefs{ 1.0f, 0.0f, 0.0f, 1.0f }; // Kp, Ki, Kd, Kv glm::vec4 _pidHistory{ 0.0f }; glm::vec4 _pidOutputs{ 0.0f }; - - uint64_t _decreaseFPSExpiry { 0 }; - uint64_t _increaseFPSExpiry { 0 }; }; #endif // hifi_LODManager_h diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index a271e9436c..951925214c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -55,54 +55,6 @@ void setupPreferences() { // Graphics quality static const QString GRAPHICS_QUALITY { "Graphics Quality" }; { - /* static const float MAX_DESKTOP_FPS = 60; - static const float MAX_HMD_FPS = 90; - static const float MIN_FPS = 10; - static const float LOW = 0.25f; - static const float MEDIUM = 0.5f; - static const float HIGH = 0.75f; - auto getter = []()->float { - auto lodManager = DependencyManager::get(); - bool inHMD = qApp->isHMDMode(); - - float increaseFPS = 0; - if (inHMD) { - increaseFPS = lodManager->getHMDLODDecreaseFPS(); - } else { - increaseFPS = lodManager->getDesktopLODDecreaseFPS(); - } - float maxFPS = inHMD ? MAX_HMD_FPS : MAX_DESKTOP_FPS; - float percentage = increaseFPS / maxFPS; - - if (percentage >= HIGH) { - return LOW; - } else if (percentage >= LOW) { - return MEDIUM; - } - return HIGH; - }; - - auto setter = [](float value) { - static const float THRASHING_DIFFERENCE = 10; - auto lodManager = DependencyManager::get(); - - bool isLowestValue = value == LOW; - bool isHMDMode = qApp->isHMDMode(); - - float maxFPS = isHMDMode ? MAX_HMD_FPS : MAX_DESKTOP_FPS; - float desiredFPS = maxFPS - THRASHING_DIFFERENCE; - - if (!isLowestValue) { - float calculatedFPS = (maxFPS - (maxFPS * value)) - THRASHING_DIFFERENCE; - desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS; - } - - if (isHMDMode) { - lodManager->setHMDLODDecreaseFPS(desiredFPS); - } else { - lodManager->setDesktopLODDecreaseFPS(desiredFPS); - } - };*/ auto getter = []()->float { return DependencyManager::get()->getWorldDetailQuality(); }; diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index 589945e9c7..dd061c0092 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -73,16 +73,16 @@ namespace render { Args(const gpu::ContextPointer& context, float sizeScale = 1.0f, int boundaryLevelAdjust = 0, - float solidAngleHalfTan = 0.1f, + float lodAngleHalfTan = 0.1f, RenderMode renderMode = DEFAULT_RENDER_MODE, DisplayMode displayMode = MONO, DebugFlags debugFlags = RENDER_DEBUG_NONE, gpu::Batch* batch = nullptr) : _context(context), _sizeScale(sizeScale), - _solidAngleHalfTan(solidAngleHalfTan), - _solidAngleHalfTanSq(solidAngleHalfTan * solidAngleHalfTan), _boundaryLevelAdjust(boundaryLevelAdjust), + _lodAngleHalfTan(lodAngleHalfTan), + _lodAngleHalfTanSq(lodAngleHalfTan * lodAngleHalfTan), _renderMode(renderMode), _displayMode(displayMode), _debugFlags(debugFlags), @@ -111,8 +111,8 @@ namespace render { float _sizeScale { 1.0f }; int _boundaryLevelAdjust { 0 }; - float _solidAngleHalfTan{ 0.1f }; - float _solidAngleHalfTanSq{ _solidAngleHalfTan * _solidAngleHalfTan }; + float _lodAngleHalfTan{ 0.1f }; + float _lodAngleHalfTanSq{ _lodAngleHalfTan * _lodAngleHalfTan }; RenderMode _renderMode { DEFAULT_RENDER_MODE }; DisplayMode _displayMode { MONO }; diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 7d03f67d3f..b2930032a3 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -155,7 +155,7 @@ void FetchSpatialTree::run(const RenderContextPointer& renderContext, const Inpu // Octree selection! float threshold = 0.0f; if (queryFrustum.isPerspective()) { - threshold = getPerspectiveAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust); + threshold = args->_lodAngleHalfTan; if (frustumResolution.y > 0) { threshold = glm::max(queryFrustum.getFieldOfView() / frustumResolution.y, threshold); } diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 28cf744394..889d8db836 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -217,24 +217,19 @@ Item { valueUnit: "Hz" plots: [ { - prop: "lodIncreaseFPS", - label: "LOD++", + prop: "lodTargetFPS", + label: "target", color: "#66FF66" }, { - prop: "fps", + prop: "nowRenderFPS", label: "FPS", color: "#FFFF55" }, { - prop: "smoothFPS", + prop: "smoothRenderFPS", label: "Smooth FPS", color: "#9999FF" - }, - { - prop: "lodDecreaseFPS", - label: "LOD--", - color: "#FF6666" } ] } From f1443083341336c9aff7dcb06e45f30f94d84d86 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 31 Aug 2018 16:58:59 +1200 Subject: [PATCH 171/744] Make tablet menu page title navigation go back a level --- .../resources/qml/hifi/tablet/TabletMenu.qml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 57c1163eb8..bfbd2d8813 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -23,6 +23,8 @@ FocusScope { property string subMenu: "" signal sendToScript(var message); + HifiConstants { id: hifi } + Rectangle { id: bgNavBar height: 90 @@ -45,24 +47,22 @@ FocusScope { anchors.topMargin: 0 anchors.top: parent.top - Image { + HiFiGlyphs { id: menuRootIcon - width: 40 - height: 40 - source: "../../../icons/tablet-icons/menu-i.svg" + text: hifi.glyphs.backward + size: 72 anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: 15 + width: breadcrumbText.text === "Menu" ? 15 : 50 + visible: breadcrumbText.text !== "Menu" MouseArea { anchors.fill: parent hoverEnabled: true onEntered: iconColorOverlay.color = "#1fc6a6"; onExited: iconColorOverlay.color = "#34a2c7"; - // navigate back to root level menu onClicked: { - buildMenu(); - breadcrumbText.text = "Menu"; + menuPopperUpper.closeLastMenu(); tabletRoot.playButtonClickSound(); } } @@ -103,7 +103,6 @@ FocusScope { menuPopperUpper.closeLastMenu(); } - function setRootMenu(rootMenu, subMenu) { tabletMenu.subMenu = subMenu; tabletMenu.rootMenu = rootMenu; From 0ae18febc762753a0b066315f724c7968b388623 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 30 Aug 2018 22:09:48 -0700 Subject: [PATCH 172/744] Removing cruft and warnings --- interface/src/LODManager.cpp | 7 ------- interface/src/ui/Stats.cpp | 36 +++++++++++++++++++----------------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 738c3dd9fb..48fd44438f 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -36,12 +36,6 @@ LODManager::LODManager() { // faster than the runningAverage is computed, the error between the value and its runningAverage will be // reduced by 1/e every timescale of real-time that passes. const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.08f; // sec -// -// Assuming the measured value is affected by logic invoked by the runningAverage bumping up against its -// thresholds, we expect the adjustment to introduce a step-function. We want the runningAverage to settle -// to the new value BEFORE we test it aginst its thresholds again. Hence we test on a period that is a few -// multiples of the running average timescale: -const uint64_t LOD_AUTO_ADJUST_PERIOD = 4 * (uint64_t)(LOD_ADJUST_RUNNING_AVG_TIMESCALE * (float)USECS_PER_MSEC); // usec // batchTIme is always contained in presentTime. // We favor using batchTime instead of presentTime as a representative value for rendering duration (on present thread) @@ -220,7 +214,6 @@ float LODManager::getPidO() const { } void LODManager::resetLODAdjust() { - // _decreaseFPSExpiry = _increaseFPSExpiry = usecTimestampNow() + LOD_AUTO_ADJUST_PERIOD; } void LODManager::setAutomaticLODAdjust(bool value) { diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 3fa712d267..1b26c9b621 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -370,27 +370,29 @@ void Stats::updateStats(bool force) { STAT_UPDATE(engineFrameTime, (float) config->getCPURunTime()); STAT_UPDATE(avatarSimulationTime, (float)avatarManager->getAvatarSimulationTime()); - STAT_UPDATE(gpuBuffers, (int)gpu::Context::getBufferGPUCount()); - STAT_UPDATE(gpuBufferMemory, (int)BYTES_TO_MB(gpu::Context::getBufferGPUMemSize())); - STAT_UPDATE(gpuTextures, (int)gpu::Context::getTextureGPUCount()); + if (_expanded) { + STAT_UPDATE(gpuBuffers, (int)gpu::Context::getBufferGPUCount()); + STAT_UPDATE(gpuBufferMemory, (int)BYTES_TO_MB(gpu::Context::getBufferGPUMemSize())); + STAT_UPDATE(gpuTextures, (int)gpu::Context::getTextureGPUCount()); - STAT_UPDATE(glContextSwapchainMemory, (int)BYTES_TO_MB(gl::Context::getSwapchainMemoryUsage())); + STAT_UPDATE(glContextSwapchainMemory, (int)BYTES_TO_MB(gl::Context::getSwapchainMemoryUsage())); - STAT_UPDATE(qmlTextureMemory, (int)BYTES_TO_MB(OffscreenQmlSurface::getUsedTextureMemory())); - STAT_UPDATE(texturePendingTransfers, (int)BYTES_TO_MB(gpu::Context::getTexturePendingGPUTransferMemSize())); - STAT_UPDATE(gpuTextureMemory, (int)BYTES_TO_MB(gpu::Context::getTextureGPUMemSize())); - STAT_UPDATE(gpuTextureResidentMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResidentGPUMemSize())); - STAT_UPDATE(gpuTextureFramebufferMemory, (int)BYTES_TO_MB(gpu::Context::getTextureFramebufferGPUMemSize())); - STAT_UPDATE(gpuTextureResourceMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceGPUMemSize())); - STAT_UPDATE(gpuTextureResourceIdealMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceIdealGPUMemSize())); - STAT_UPDATE(gpuTextureResourcePopulatedMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourcePopulatedGPUMemSize())); - STAT_UPDATE(gpuTextureExternalMemory, (int)BYTES_TO_MB(gpu::Context::getTextureExternalGPUMemSize())); + STAT_UPDATE(qmlTextureMemory, (int)BYTES_TO_MB(OffscreenQmlSurface::getUsedTextureMemory())); + STAT_UPDATE(texturePendingTransfers, (int)BYTES_TO_MB(gpu::Context::getTexturePendingGPUTransferMemSize())); + STAT_UPDATE(gpuTextureMemory, (int)BYTES_TO_MB(gpu::Context::getTextureGPUMemSize())); + STAT_UPDATE(gpuTextureResidentMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResidentGPUMemSize())); + STAT_UPDATE(gpuTextureFramebufferMemory, (int)BYTES_TO_MB(gpu::Context::getTextureFramebufferGPUMemSize())); + STAT_UPDATE(gpuTextureResourceMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceGPUMemSize())); + STAT_UPDATE(gpuTextureResourceIdealMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceIdealGPUMemSize())); + STAT_UPDATE(gpuTextureResourcePopulatedMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourcePopulatedGPUMemSize())); + STAT_UPDATE(gpuTextureExternalMemory, (int)BYTES_TO_MB(gpu::Context::getTextureExternalGPUMemSize())); #if !defined(Q_OS_ANDROID) - STAT_UPDATE(gpuTextureMemoryPressureState, getTextureMemoryPressureModeString()); + STAT_UPDATE(gpuTextureMemoryPressureState, getTextureMemoryPressureModeString()); #endif - STAT_UPDATE(gpuFreeMemory, (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemSize())); - STAT_UPDATE(rectifiedTextureCount, (int)RECTIFIED_TEXTURE_COUNT.load()); - STAT_UPDATE(decimatedTextureCount, (int)DECIMATED_TEXTURE_COUNT.load()); + STAT_UPDATE(gpuFreeMemory, (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemSize())); + STAT_UPDATE(rectifiedTextureCount, (int)RECTIFIED_TEXTURE_COUNT.load()); + STAT_UPDATE(decimatedTextureCount, (int)DECIMATED_TEXTURE_COUNT.load()); + } gpu::ContextStats gpuFrameStats; gpuContext->getFrameStats(gpuFrameStats); From 371105d0c44e8fd04a4ae7375bf0f64c0dda3b75 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 30 Aug 2018 22:18:34 -0700 Subject: [PATCH 173/744] Removing cruft and warnings --- interface/src/ui/Stats.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index ed88f4fc6b..a0453a09c5 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -93,7 +93,6 @@ private: \ * @property {number} processing - Read-only. * @property {number} processingPending - Read-only. * @property {number} triangles - Read-only. - * @property {number} quads - Read-only. * @property {number} materialSwitches - Read-only. * @property {number} itemConsidered - Read-only. * @property {number} itemOutOfView - Read-only. @@ -250,7 +249,6 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, processingPending, 0) STATS_PROPERTY(int, triangles, 0) STATS_PROPERTY(int, drawcalls, 0) - // STATS_PROPERTY(int, quads, 0) STATS_PROPERTY(int, materialSwitches, 0) STATS_PROPERTY(int, itemConsidered, 0) STATS_PROPERTY(int, itemOutOfView, 0) @@ -743,13 +741,6 @@ signals: */ void drawcallsChanged(); - /**jsdoc - * Triggered when the value of the quads property changes. - * @function Stats.quadsChanged - * @returns {Signal} - */ - // void quadsChanged(); - /**jsdoc * Triggered when the value of the materialSwitches property changes. * @function Stats.materialSwitchesChanged From 969d16262f046606469e89f0bcf0421c3082e90c Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 30 Aug 2018 22:20:30 -0700 Subject: [PATCH 174/744] Removing cruft and warnings --- scripts/developer/utilities/render/lod.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/scripts/developer/utilities/render/lod.js b/scripts/developer/utilities/render/lod.js index 632f4e50c5..f3e4208034 100644 --- a/scripts/developer/utilities/render/lod.js +++ b/scripts/developer/utilities/render/lod.js @@ -56,7 +56,6 @@ presentationMode: Desktop.PresentationMode.NATIVE, size: {x: 400, y: 600} }); - // window.setPosition(200, 50); window.closed.connect(killWindow); window.fromQml.connect(fromQml); onScreen = true @@ -94,11 +93,7 @@ function onScreenChanged(type, url) { if (onTablet) { - if (url === QMLAPP_URL) { - onAppScreen = true; - } else { - onAppScreen = false; - } + onAppScreen = (url === QMLAPP_URL); button.editProperties({isActive: onAppScreen}); wireEventBridge(onAppScreen); From 764f75c3404472e994dc5c35cba14ef6be8bbe0e Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 30 Aug 2018 22:26:45 -0700 Subject: [PATCH 175/744] including review feedback --- interface/src/LODManager.cpp | 2 +- interface/src/LODManager.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 48fd44438f..c1ff6cfd6a 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -72,7 +72,7 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { float smoothBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) ? realTimeDelta / (LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) : 1.0f; _smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec - if (!_automaticLODAdjust || _nowRenderTime == 0.0f) { + if (!_automaticLODAdjust || _nowRenderTime == 0.0f || _smoothRenderTime == 0.0f) { // early exit return; } diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 0af745bcf4..1452c714e9 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -173,13 +173,13 @@ public: void resetLODAdjust(); float getNowRenderTime() const { return _nowRenderTime; }; - float getNowRenderFPS() const { return (float)MSECS_PER_SECOND / _nowRenderTime; }; + float getNowRenderFPS() const { return (_nowRenderTime > 0.0f ? (float)MSECS_PER_SECOND / _nowRenderTime : 0.0f); }; void setSmoothScale(float t); float getSmoothScale() const { return _smoothScale; } float getSmoothRenderTime() const { return _smoothRenderTime; }; - float getSmoothRenderFPS() const { return (float)MSECS_PER_SECOND / _smoothRenderTime; }; + float getSmoothRenderFPS() const { return (_smoothRenderTime > 0.0f ? (float)MSECS_PER_SECOND / _smoothRenderTime : 0.0f); }; void setWorldDetailQuality(float quality); float getWorldDetailQuality() const; From b6d23843dde70d5970c61d025a48a35606491ac9 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 30 Aug 2018 22:32:02 -0700 Subject: [PATCH 176/744] adding fix to domain redirection URL --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a927853fb0..86a1421127 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1199,7 +1199,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); - &domainHandler.setErrorDomainURL(QUrl(REDIRECT_HIFI_ADDRESS)); + nodeList->getDomainHandler().setErrorDomainURL(QUrl(REDIRECT_HIFI_ADDRESS)); // We could clear ATP assets only when changing domains, but it's possible that the domain you are connected // to has gone down and switched to a new content set, so when you reconnect the cached ATP assets will no longer be valid. From eb5f6c8eb30e4bd8ea997b81d4480b05181f9610 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Fri, 31 Aug 2018 09:07:34 -0700 Subject: [PATCH 177/744] ardressing coding standard --- interface/src/LODManager.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index c1ff6cfd6a..87771f1c6f 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -280,14 +280,11 @@ QString LODManager::getLODFeedbackText() { QString result; if (relativeToDefault > 1.01f) { result = QString("20:%1 or %2 times further than average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault, 0, 'f', 2).arg(granularityFeedback); - } - else if (relativeToDefault > 0.99f) { + } else if (relativeToDefault > 0.99f) { result = QString("20:20 or the default distance for average vision%1").arg(granularityFeedback); - } - else if (relativeToDefault > 0.01f) { + } else if (relativeToDefault > 0.01f) { result = QString("20:%1 or %2 of default distance for average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault, 0, 'f', 3).arg(granularityFeedback); - } - else { + } else { result = QString("%2 of default distance for average vision%3").arg(relativeToDefault, 0, 'f', 3).arg(granularityFeedback); } return result; From 262fdb3c28ccc76fc788ef69929f0095b11cd0e1 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Fri, 31 Aug 2018 09:09:22 -0700 Subject: [PATCH 178/744] ardressing coding standard --- interface/src/LODManager.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 87771f1c6f..8f1289f9f2 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -350,8 +350,7 @@ void LODManager::setWorldDetailQuality(float quality) { if (isHMDMode) { setHMDLODTargetFPS(desiredFPS); - } - else { + } else { setDesktopLODTargetFPS(desiredFPS); } @@ -377,8 +376,7 @@ float LODManager::getWorldDetailQuality() const { if (percentage <= LOW) { return LOW; - } - else if (percentage <= MEDIUM) { + } else if (percentage <= MEDIUM) { return MEDIUM; } From e94d7de99afb4f8914150a9cca3cc524d4fa8503 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 30 Aug 2018 16:05:25 -0700 Subject: [PATCH 179/744] Add JS API hook for getting overlay IDs from a pointer via Pointer.getPointerProperties --- interface/src/raypick/LaserPointer.cpp | 33 ++++++++++++++++++ interface/src/raypick/LaserPointer.h | 2 ++ interface/src/raypick/ParabolaPointer.cpp | 31 +++++++++++++++++ interface/src/raypick/ParabolaPointer.h | 2 ++ .../src/raypick/PointerScriptingInterface.cpp | 34 ++++++++++++------- .../src/raypick/PointerScriptingInterface.h | 8 +++++ interface/src/raypick/StylusPointer.cpp | 4 +++ interface/src/raypick/StylusPointer.h | 2 ++ libraries/pointers/src/Pointer.h | 2 ++ libraries/pointers/src/PointerManager.cpp | 9 +++++ libraries/pointers/src/PointerManager.h | 1 + 11 files changed, 116 insertions(+), 12 deletions(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 2382a95105..ee92a0fa6d 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -35,6 +35,39 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& } } +QVariantMap LaserPointer::toVariantMap() const { + QVariantMap qVariantMap; + + QVariantList qRenderStates; + for (auto iter = _renderStates.cbegin(); iter != _renderStates.cend(); iter++) { + auto renderState = iter->second; + QVariantMap qRenderState; + qRenderState["name"] = iter->first.c_str(); + qRenderState["start"] = renderState->getStartID(); + qRenderState["path"] = std::static_pointer_cast(renderState)->getPathID(); + qRenderState["end"] = renderState->getEndID(); + qRenderStates.append(qRenderState); + } + qVariantMap["renderStates"] = qRenderStates; + + QVariantList qDefaultRenderStates; + for (auto iter = _defaultRenderStates.cbegin(); iter != _defaultRenderStates.cend(); iter++) { + float distance = iter->second.first; + auto defaultRenderState = iter->second.second; + QVariantMap qDefaultRenderState; + + qDefaultRenderState["name"] = iter->first.c_str(); + qDefaultRenderState["distance"] = distance; + qDefaultRenderState["start"] = defaultRenderState->getStartID(); + qDefaultRenderState["path"] = std::static_pointer_cast(defaultRenderState)->getPathID(); + qDefaultRenderState["end"] = defaultRenderState->getEndID(); + qDefaultRenderStates.append(qDefaultRenderState); + } + qVariantMap["defaultRenderStates"] = qDefaultRenderStates; + + return qVariantMap; +} + glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) const { auto rayPickResult = std::static_pointer_cast(pickResult); return (rayPickResult ? vec3FromVariant(rayPickResult->pickVariant["origin"]) : glm::vec3(0.0f)); diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 95a5bccc6c..c0ac3259d9 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -42,6 +42,8 @@ public: LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + QVariantMap toVariantMap() const override; + static std::shared_ptr buildRenderState(const QVariantMap& propMap); protected: diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 097c98340c..b8c6224f08 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -60,6 +60,37 @@ void ParabolaPointer::editRenderStatePath(const std::string& state, const QVaria } } +QVariantMap ParabolaPointer::toVariantMap() const { + QVariantMap qVariantMap; + + QVariantList qRenderStates; + for (auto iter = _renderStates.cbegin(); iter != _renderStates.cend(); iter++) { + auto renderState = iter->second; + QVariantMap qRenderState; + qRenderState["name"] = iter->first.c_str(); + qRenderState["start"] = renderState->getStartID(); + qRenderState["end"] = renderState->getEndID(); + qRenderStates.append(qRenderState); + } + qVariantMap["renderStates"] = qRenderStates; + + QVariantList qDefaultRenderStates; + for (auto iter = _defaultRenderStates.cbegin(); iter != _defaultRenderStates.cend(); iter++) { + float distance = iter->second.first; + auto defaultRenderState = iter->second.second; + QVariantMap qDefaultRenderState; + + qDefaultRenderState["name"] = iter->first.c_str(); + qDefaultRenderState["distance"] = distance; + qDefaultRenderState["start"] = defaultRenderState->getStartID(); + qDefaultRenderState["end"] = defaultRenderState->getEndID(); + qDefaultRenderStates.append(qDefaultRenderState); + } + qVariantMap["defaultRenderStates"] = qDefaultRenderStates; + + return qVariantMap; +} + glm::vec3 ParabolaPointer::getPickOrigin(const PickResultPointer& pickResult) const { auto parabolaPickResult = std::static_pointer_cast(pickResult); return (parabolaPickResult ? vec3FromVariant(parabolaPickResult->pickVariant["origin"]) : glm::vec3(0.0f)); diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 93f9a7b055..526abe3b0d 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -97,6 +97,8 @@ public: ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + QVariantMap toVariantMap() const override; + static std::shared_ptr buildRenderState(const QVariantMap& propMap); protected: diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index a9b9b6e9ea..cd3ffdac82 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -76,16 +76,19 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) * @property {number} distance The distance at which to render the end of this Ray Pointer, if one is defined. */ /**jsdoc - * A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something. + * A set of properties which define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something. * * @typedef {object} Pointers.RayPointerRenderState * @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} - * @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). - * An overlay to represent the beginning of the Ray Pointer, if desired. - * @property {Overlays.OverlayProperties} [path] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field), which must be "line3d". - * An overlay to represent the path of the Ray Pointer, if desired. - * @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). - * An overlay to represent the end of the Ray Pointer, if desired. + * @property {Overlays.OverlayProperties|QUuid} [start] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the beginning of the Ray Pointer, + * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). + * When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. + * @property {Overlays.OverlayProperties|QUuid} [path] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the path of the Ray Pointer, + * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field), which must be "line3d". + * When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. + * @property {Overlays.OverlayProperties|QUuid} [end] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the end of the Ray Pointer, + * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). + * When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. */ /**jsdoc * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. @@ -225,11 +228,14 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope * * @typedef {object} Pointers.ParabolaPointerRenderState * @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} -* @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). -* An overlay to represent the beginning of the Parabola Pointer, if desired. -* @property {Pointers.ParabolaProperties} [path] The rendering properties of the parabolic path defined by the Parabola Pointer. -* @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). -* An overlay to represent the end of the Parabola Pointer, if desired. +* @property {Overlays.OverlayProperties|QUuid} [start] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the beginning of the Parabola Pointer, +* using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). +* When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. +* @property {Pointers.ParabolaProperties} [path] When using {@link Pointers.createPointer}, the optionally defined rendering properties of the parabolic path defined by the Parabola Pointer. +* Not defined in {@link Pointers.getPointerProperties}. +* @property {Overlays.OverlayProperties|QUuid} [end] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the end of the Parabola Pointer, +* using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). +* When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. */ /**jsdoc * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. @@ -375,4 +381,8 @@ QVariantMap PointerScriptingInterface::getPrevPickResult(unsigned int uid) const result = pickResult->toVariantMap(); } return result; +} + +QVariantMap PointerScriptingInterface::getPointerProperties(unsigned int uid) const { + return DependencyManager::get()->getPointerProperties(uid); } \ No newline at end of file diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 9fe05182c7..94f1d62552 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -203,6 +203,14 @@ public: */ Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get()->isMouse(uid); } + /**jsdoc + * Returns information about an existing Pointer + * @function Pointers.getPointerState + * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. + * @returns {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} The information about the Pointer. + * Currently only includes renderStates and defaultRenderStates with associated overlay IDs. + */ + Q_INVOKABLE QVariantMap getPointerProperties(unsigned int uid) const; }; #endif // hifi_PointerScriptingInterface_h diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 8c0fb59106..d963b0c670 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -203,6 +203,10 @@ void StylusPointer::setRenderState(const std::string& state) { } } +QVariantMap StylusPointer::toVariantMap() const { + return QVariantMap(); +} + glm::vec3 StylusPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) { switch (pickedObject.type) { case ENTITY: diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h index 950b03b7c9..1fe8b047e5 100644 --- a/interface/src/raypick/StylusPointer.h +++ b/interface/src/raypick/StylusPointer.h @@ -33,6 +33,8 @@ public: void setRenderState(const std::string& state) override; void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override {} + QVariantMap toVariantMap() const override; + static OverlayID buildStylusOverlay(const QVariantMap& properties); protected: diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 0c842dbd88..4264a60079 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -50,6 +50,8 @@ public: virtual void setRenderState(const std::string& state) = 0; virtual void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) = 0; + virtual QVariantMap toVariantMap() const = 0; + virtual void setPrecisionPicking(bool precisionPicking); virtual void setIgnoreItems(const QVector& ignoreItems) const; virtual void setIncludeItems(const QVector& includeItems) const; diff --git a/libraries/pointers/src/PointerManager.cpp b/libraries/pointers/src/PointerManager.cpp index be890da392..922f0bb5bc 100644 --- a/libraries/pointers/src/PointerManager.cpp +++ b/libraries/pointers/src/PointerManager.cpp @@ -77,6 +77,15 @@ PickResultPointer PointerManager::getPrevPickResult(unsigned int uid) const { return result; } +QVariantMap PointerManager::getPointerProperties(unsigned int uid) const { + auto pointer = find(uid); + if (pointer) { + return pointer->toVariantMap(); + } else { + return QVariantMap(); + } +} + void PointerManager::update() { auto cachedPointers = resultWithReadLock>>([&] { return _pointers; diff --git a/libraries/pointers/src/PointerManager.h b/libraries/pointers/src/PointerManager.h index b98558622f..2d0b2a107e 100644 --- a/libraries/pointers/src/PointerManager.h +++ b/libraries/pointers/src/PointerManager.h @@ -30,6 +30,7 @@ public: void setRenderState(unsigned int uid, const std::string& renderState) const; void editRenderState(unsigned int uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) const; PickResultPointer getPrevPickResult(unsigned int uid) const; + QVariantMap getPointerProperties(unsigned int uid) const; void setPrecisionPicking(unsigned int uid, bool precisionPicking) const; void setIgnoreItems(unsigned int uid, const QVector& ignoreEntities) const; From 6bd1ab5ae05a4296b7ea68b35f6d734d3f3a2ede Mon Sep 17 00:00:00 2001 From: sam gateau Date: Fri, 31 Aug 2018 09:18:33 -0700 Subject: [PATCH 180/744] fixing a bad constant --- interface/src/LODManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 8f1289f9f2..ec50190ddf 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -42,7 +42,7 @@ const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.08f; // sec // if batchTime + cushionTime < presentTime. // since we are shooting for fps around 60, 90Hz, the ideal frames are around 10ms // so we are picking a cushion time of 3ms -const float LOD_BATCH_TO_PRESENT_CUSHION_TIME = 10.0f; // msec +const float LOD_BATCH_TO_PRESENT_CUSHION_TIME = 3.0f; // msec void LODManager::setRenderTimes(float presentTime, float engineRunTime, float batchTime, float gpuTime) { _presentTime = presentTime; From 9bf088d32b9c803c4974061ced8170b7ed999185 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 31 Aug 2018 10:28:45 -0700 Subject: [PATCH 181/744] Just assign mathPick.loaded in CollisionPick::getMathematicalPick --- interface/src/raypick/CollisionPick.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index b163f23df6..654378cc9b 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -356,9 +356,7 @@ CollisionPick::CollisionPick(const PickFilter& filter, float maxDistance, bool e CollisionRegion CollisionPick::getMathematicalPick() const { CollisionRegion mathPick = _mathPick; - if (!mathPick.loaded) { - mathPick.loaded = isLoaded(); - } + mathPick.loaded = isLoaded(); if (!parentTransform) { return mathPick; } else { From 4d85cb17f36b4f73582419ee944492725e1f5db3 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 31 Aug 2018 10:46:17 -0700 Subject: [PATCH 182/744] Include full transform in CollisionPick::getResultTransform --- interface/src/raypick/CollisionPick.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 654378cc9b..7d0276875b 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -421,7 +421,5 @@ PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) } Transform CollisionPick::getResultTransform() const { - Transform transform; - transform.setTranslation(getMathematicalPick().transform.getTranslation()); - return transform; + return Transform(getMathematicalPick().transform); } \ No newline at end of file From f71fc833099aaa678f41779adc9fd1a2bf439c97 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 31 Aug 2018 10:48:48 -0700 Subject: [PATCH 183/744] Remove PickResult::needsToCompareResults --- interface/src/raypick/CollisionPick.h | 1 - libraries/pointers/src/Pick.h | 1 - libraries/pointers/src/PickCacheOptimizer.h | 6 +++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index a1636e4b47..ce8b3bd199 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -40,7 +40,6 @@ public: QVariantMap toVariantMap() const override; bool doesIntersect() const override { return intersects; } - bool needsToCompareResults() const override { return true; } bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return true; } PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override; diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index 9c4de6c46e..099a791407 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -115,7 +115,6 @@ public: } virtual bool doesIntersect() const = 0; - virtual bool needsToCompareResults() const { return doesIntersect(); } // for example: if we want the closest result, compare based on distance // if we want all results, combine them diff --git a/libraries/pointers/src/PickCacheOptimizer.h b/libraries/pointers/src/PickCacheOptimizer.h index ae515fc8d7..448d67b5a9 100644 --- a/libraries/pointers/src/PickCacheOptimizer.h +++ b/libraries/pointers/src/PickCacheOptimizer.h @@ -92,7 +92,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetEntityIntersection(mathematicalPick); if (entityRes) { - cacheResult(entityRes->needsToCompareResults(), entityRes, entityKey, res, mathematicalPick, results, pick); + cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick); } } } @@ -102,7 +102,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetOverlayIntersection(mathematicalPick); if (overlayRes) { - cacheResult(overlayRes->needsToCompareResults(), overlayRes, overlayKey, res, mathematicalPick, results, pick); + cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick); } } } @@ -112,7 +112,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetAvatarIntersection(mathematicalPick); if (avatarRes) { - cacheResult(avatarRes->needsToCompareResults(), avatarRes, avatarKey, res, mathematicalPick, results, pick); + cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick); } } } From 2e290ea798254481fe7dd91af5916686a4543f29 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 31 Aug 2018 10:52:00 -0700 Subject: [PATCH 184/744] Only create a NestableTransformNode if the spatially nestable exists --- interface/src/raypick/PickScriptingInterface.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index f551c0ec90..6dedf3fca1 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -373,7 +373,8 @@ std::shared_ptr PickScriptingInterface::createTransformNode(const if (propMap["parentJointIndex"].isValid()) { parentJointIndex = propMap["parentJointIndex"].toInt(); } - if (success && !nestablePointer.expired()) { + auto sharedNestablePointer = nestablePointer.lock(); + if (success && sharedNestablePointer) { return std::make_shared(nestablePointer, parentJointIndex); } } From b5862aca2aa8c4d397f8f962468cccf4d205576c Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 31 Aug 2018 10:53:20 -0700 Subject: [PATCH 185/744] Use bracket initialization for CollisionRegion.loaded --- libraries/shared/src/RegisteredMetaTypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 8fb1301484..02f5fa570c 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -365,7 +365,7 @@ public: } // We can't load the model here because it would create a circular dependency, so we delegate that responsibility to the owning CollisionPick - bool loaded = false; + bool loaded { false }; QUrl modelURL; // We can't compute the shapeInfo here without loading the model first, so we delegate that responsibility to the owning CollisionPick From 84ad5c79f9e323760efca6259f5d78fa304abdda Mon Sep 17 00:00:00 2001 From: sam gateau Date: Fri, 31 Aug 2018 11:09:16 -0700 Subject: [PATCH 186/744] fixing a no op --- interface/src/LODManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index ec50190ddf..a64e28bea4 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -385,7 +385,7 @@ float LODManager::getWorldDetailQuality() const { void LODManager::setLODQualityLevel(float quality) { - _lodQualityLevel; + _lodQualityLevel = quality; } float LODManager::getLODQualityLevel() const { From 62344c106dadb5fe54687f7c9b9d1c8b4ab34e46 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 31 Aug 2018 11:19:53 -0700 Subject: [PATCH 187/744] Make render states a map when returned from PointerScriptingInterface::getPointerProperties --- interface/src/raypick/LaserPointer.cpp | 10 ++++------ interface/src/raypick/ParabolaPointer.cpp | 10 ++++------ .../src/raypick/PointerScriptingInterface.cpp | 20 +++++++++++++------ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index ee92a0fa6d..afbb278dc7 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -38,30 +38,28 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& QVariantMap LaserPointer::toVariantMap() const { QVariantMap qVariantMap; - QVariantList qRenderStates; + QVariantMap qRenderStates; for (auto iter = _renderStates.cbegin(); iter != _renderStates.cend(); iter++) { auto renderState = iter->second; QVariantMap qRenderState; - qRenderState["name"] = iter->first.c_str(); qRenderState["start"] = renderState->getStartID(); qRenderState["path"] = std::static_pointer_cast(renderState)->getPathID(); qRenderState["end"] = renderState->getEndID(); - qRenderStates.append(qRenderState); + qRenderStates[iter->first.c_str()] = qRenderState; } qVariantMap["renderStates"] = qRenderStates; - QVariantList qDefaultRenderStates; + QVariantMap qDefaultRenderStates; for (auto iter = _defaultRenderStates.cbegin(); iter != _defaultRenderStates.cend(); iter++) { float distance = iter->second.first; auto defaultRenderState = iter->second.second; QVariantMap qDefaultRenderState; - qDefaultRenderState["name"] = iter->first.c_str(); qDefaultRenderState["distance"] = distance; qDefaultRenderState["start"] = defaultRenderState->getStartID(); qDefaultRenderState["path"] = std::static_pointer_cast(defaultRenderState)->getPathID(); qDefaultRenderState["end"] = defaultRenderState->getEndID(); - qDefaultRenderStates.append(qDefaultRenderState); + qDefaultRenderStates[iter->first.c_str()] = qDefaultRenderState; } qVariantMap["defaultRenderStates"] = qDefaultRenderStates; diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index b8c6224f08..fc3aecdb51 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -63,28 +63,26 @@ void ParabolaPointer::editRenderStatePath(const std::string& state, const QVaria QVariantMap ParabolaPointer::toVariantMap() const { QVariantMap qVariantMap; - QVariantList qRenderStates; + QVariantMap qRenderStates; for (auto iter = _renderStates.cbegin(); iter != _renderStates.cend(); iter++) { auto renderState = iter->second; QVariantMap qRenderState; - qRenderState["name"] = iter->first.c_str(); qRenderState["start"] = renderState->getStartID(); qRenderState["end"] = renderState->getEndID(); - qRenderStates.append(qRenderState); + qRenderStates[iter->first.c_str()] = qRenderState; } qVariantMap["renderStates"] = qRenderStates; - QVariantList qDefaultRenderStates; + QVariantMap qDefaultRenderStates; for (auto iter = _defaultRenderStates.cbegin(); iter != _defaultRenderStates.cend(); iter++) { float distance = iter->second.first; auto defaultRenderState = iter->second.second; QVariantMap qDefaultRenderState; - qDefaultRenderState["name"] = iter->first.c_str(); qDefaultRenderState["distance"] = distance; qDefaultRenderState["start"] = defaultRenderState->getStartID(); qDefaultRenderState["end"] = defaultRenderState->getEndID(); - qDefaultRenderStates.append(qDefaultRenderState); + qDefaultRenderStates[iter->first.c_str()] = qDefaultRenderState; } qVariantMap["defaultRenderStates"] = qDefaultRenderStates; diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index cd3ffdac82..7209e402a1 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -79,7 +79,7 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) * A set of properties which define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something. * * @typedef {object} Pointers.RayPointerRenderState - * @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} + * @property {string} name When using {@link Pointers.createPointer}, the name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} * @property {Overlays.OverlayProperties|QUuid} [start] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the beginning of the Ray Pointer, * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). * When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. @@ -102,8 +102,12 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) * @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. 0-1. If 0 or 1, * the normal will follow exactly. * @property {boolean} [enabled=false] - * @property {Pointers.RayPointerRenderState[]} [renderStates] A list of different visual states to switch between. - * @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection. + * @property {Pointers.RayPointerRenderState[]|Object.} [renderStates] A collection of different visual states to switch between. + * When using {@link Pointers.createPointer}, a list of RayPointerRenderStates. + * When returned from {@link Pointers.getPointerProperties}, a map between render state names and RayPointRenderStates. + * @property {Pointers.DefaultRayPointerRenderState[]|Object.} [defaultRenderStates] A collection of different visual states to use if there is no intersection. + * When using {@link Pointers.createPointer}, a list of DefaultRayPointerRenderStates. + * When returned from {@link Pointers.getPointerProperties}, a map between render state names and DefaultRayPointRenderStates. * @property {boolean} [hover=false] If this Pointer should generate hover events. * @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. */ @@ -227,7 +231,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope * A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is intersecting something. * * @typedef {object} Pointers.ParabolaPointerRenderState -* @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} +* @property {string} name When using {@link Pointers.createPointer}, the name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} * @property {Overlays.OverlayProperties|QUuid} [start] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the beginning of the Parabola Pointer, * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). * When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. @@ -249,8 +253,12 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope * @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. 0-1. If 0 or 1, * the normal will follow exactly. * @property {boolean} [enabled=false] -* @property {Pointers.ParabolaPointerRenderState[]} [renderStates] A list of different visual states to switch between. -* @property {Pointers.DefaultParabolaPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection. +* @property {Pointers.ParabolaPointerRenderState[]|Object.} [renderStates] A collection of different visual states to switch between. +* When using {@link Pointers.createPointer}, a list of ParabolaPointerRenderStates. +* When returned from {@link Pointers.getPointerProperties}, a map between render state names and ParabolaPointerRenderStates. +* @property {Pointers.DefaultParabolaPointerRenderState[]|Object.} [defaultRenderStates] A collection of different visual states to use if there is no intersection. +* When using {@link Pointers.createPointer}, a list of DefaultParabolaPointerRenderStates. +* When returned from {@link Pointers.getPointerProperties}, a map between render state names and DefaultParabolaPointerRenderStates. * @property {boolean} [hover=false] If this Pointer should generate hover events. * @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. */ From d01b438cb320a7ccc0aa8a0085e86940bb73c09c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 31 Aug 2018 11:25:17 -0700 Subject: [PATCH 188/744] CR --- interface/src/avatar/AvatarManager.cpp | 6 ++++-- .../graphics/src/graphics/BufferViewHelpers.h | 14 ++++++------- libraries/shared/src/AABox.cpp | 20 +++++++++++++++++++ libraries/shared/src/AABox.h | 9 +++++++++ libraries/shared/src/GLMHelpers.h | 11 +++++++--- tests/shared/src/AACubeTests.cpp | 3 ++- tests/shared/src/GLMHelpersTests.cpp | 2 +- 7 files changed, 50 insertions(+), 15 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index af9d9ad6b1..bd98549510 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -264,9 +264,11 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { const SortableAvatar& newSortData = *it; const auto newAvatar = std::static_pointer_cast(newSortData.getAvatar()); bool inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; - if (inView && newAvatar->hasNewJointData()) { - numAVatarsNotUpdated++; + // Once we reach an avatar that's not in view, all avatars after it will also be out of view + if (!inView) { + break; } + numAVatarsNotUpdated += (int)(newAvatar->hasNewJointData()); ++it; } break; diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.h b/libraries/graphics/src/graphics/BufferViewHelpers.h index 7c37c75163..026e7b53a3 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.h +++ b/libraries/graphics/src/graphics/BufferViewHelpers.h @@ -56,18 +56,16 @@ namespace buffer_helpers { tangent = glm::clamp(tangent, -1.0f, 1.0f); normal *= 511.0f; tangent *= 511.0f; - normal = fastRoundf(normal); - tangent = fastRoundf(tangent); glm::detail::i10i10i10i2 normalStruct; glm::detail::i10i10i10i2 tangentStruct; - normalStruct.data.x = int(normal.x); - normalStruct.data.y = int(normal.y); - normalStruct.data.z = int(normal.z); + normalStruct.data.x = fastLrintf(normal.x); + normalStruct.data.y = fastLrintf(normal.y); + normalStruct.data.z = fastLrintf(normal.z); normalStruct.data.w = 0; - tangentStruct.data.x = int(tangent.x); - tangentStruct.data.y = int(tangent.y); - tangentStruct.data.z = int(tangent.z); + tangentStruct.data.x = fastLrintf(tangent.x); + tangentStruct.data.y = fastLrintf(tangent.y); + tangentStruct.data.z = fastLrintf(tangent.z); tangentStruct.data.w = 0; packedNormal = normalStruct.pack; packedTangent = tangentStruct.pack; diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index e537c3e56a..ff6c2a4e6e 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -79,6 +79,16 @@ void AABox::setBox(const glm::vec3& corner, const glm::vec3& scale) { glm::vec3 AABox::getFarthestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; + // This is a branchless version of: + //if (normal.x > 0.0f) { + // result.x += _scale.x; + //} + //if (normal.y > 0.0f) { + // result.y += _scale.y; + //} + //if (normal.z > 0.0f) { + // result.z += _scale.z; + //} float blend = (float)(normal.x > 0.0f); result.x += blend * _scale.x + (1.0f - blend) * 0.0f; blend = (float)(normal.y > 0.0f); @@ -90,6 +100,16 @@ glm::vec3 AABox::getFarthestVertex(const glm::vec3& normal) const { glm::vec3 AABox::getNearestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; + // This is a branchless version of: + //if (normal.x < 0.0f) { + // result.x += _scale.x; + //} + //if (normal.y < 0.0f) { + // result.y += _scale.y; + //} + //if (normal.z < 0.0f) { + // result.z += _scale.z; + //} float blend = (float)(normal.x < 0.0f); result.x += blend * _scale.x + (1.0f - blend) * 0.0f; blend = (float)(normal.y < 0.0f); diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index a56615c40e..e0bb1343f8 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -86,6 +86,15 @@ public: AABox clamp(float min, float max) const; inline AABox& operator+=(const glm::vec3& point) { + // Branchless version of: + //if (isInvalid()) { + // _corner = glm::min(_corner, point); + //} else { + // glm::vec3 maximum(_corner + _scale); + // _corner = glm::min(_corner, point); + // maximum = glm::max(maximum, point); + // _scale = maximum - _corner; + //} float blend = (float)isInvalid(); glm::vec3 maximumScale(glm::max(_scale, point - _corner)); _corner = glm::min(_corner, point); diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 619f8172d5..96219ea48c 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -316,11 +316,16 @@ inline void glm_mat4u_mul(const glm::mat4& m1, const glm::mat4& m2, glm::mat4& r #endif } -inline glm::vec3 fastRoundf(const glm::vec3& vec) { +// convert float to int, using round-to-nearest-even (undefined on overflow) +inline int fastLrintf(float x) { #if GLM_ARCH & GLM_ARCH_SSE2_BIT - return glm::vec3(_mm_cvt_ss2si(_mm_set_ss(vec.x)), _mm_cvt_ss2si(_mm_set_ss(vec.y)), _mm_cvt_ss2si(_mm_set_ss(vec.z))); + return _mm_cvt_ss2si(_mm_set_ss(x)); #else - return glm::round(vec); + // return lrintf(x); + static_assert(std::numeric_limits::is_iec559, "Requires IEEE-754 double precision format"); + union { double d; int64_t i; } bits = { (double)x }; + bits.d += (3ULL << 51); + return (int)bits.i; #endif } diff --git a/tests/shared/src/AACubeTests.cpp b/tests/shared/src/AACubeTests.cpp index c3c8e3e6f7..4ed3ee2813 100644 --- a/tests/shared/src/AACubeTests.cpp +++ b/tests/shared/src/AACubeTests.cpp @@ -168,12 +168,13 @@ void AACubeTests::rayVsParabolaPerformance() { glm::vec3 origin(0.0f); glm::vec3 direction = glm::normalize(glm::vec3(1.0f)); + glm::vec3 invDirection = 1.0f / direction; float distance; BoxFace face; glm::vec3 normal; auto start = std::chrono::high_resolution_clock::now(); for (auto& cube : cubes) { - if (cube.findRayIntersection(origin, direction, 1.0f / direction, distance, face, normal)) { + if (cube.findRayIntersection(origin, direction, invDirection, distance, face, normal)) { numRayHits++; } } diff --git a/tests/shared/src/GLMHelpersTests.cpp b/tests/shared/src/GLMHelpersTests.cpp index 669bbb8e43..71877e89f6 100644 --- a/tests/shared/src/GLMHelpersTests.cpp +++ b/tests/shared/src/GLMHelpersTests.cpp @@ -234,7 +234,7 @@ void GLMHelpersTests::roundPerf() { auto glmTime = std::chrono::high_resolution_clock::now() - start; start = std::chrono::high_resolution_clock::now(); for (auto& vec : vecs2) { - vec = fastRoundf(vec); + vec = glm::vec3(fastLrintf(vec.x), fastLrintf(vec.y), fastLrintf(vec.z)); } auto manualTime = std::chrono::high_resolution_clock::now() - start; From ec18806546f56a6d35ba4cfc21b3808a3997b3f3 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Fri, 31 Aug 2018 12:15:28 -0700 Subject: [PATCH 189/744] INtegrating in the algorithm, not in the public values the overshoot for the target FPS to make it simpler to understand --- interface/src/LODManager.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index a64e28bea4..1947374b1f 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -19,8 +19,8 @@ #include "ui/DialogsManager.h" #include "InterfaceLogging.h" -const float LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS = LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS; -const float LODManager::DEFAULT_HMD_LOD_DOWN_FPS = LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS; +const float LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS; +const float LODManager::DEFAULT_HMD_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS; Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS); Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", LODManager::DEFAULT_HMD_LOD_DOWN_FPS); @@ -81,8 +81,10 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { float oldOctreeSizeScale = getOctreeSizeScale(); float oldLODAngle = getLODAngleDeg(); - // Target fps and current fps based on latest measurments - float targetFPS = getLODTargetFPS(); + // Target fps is slightly overshooted by 5hz + float targetFPS = getLODTargetFPS() + LOD_OFFSET_FPS; + + // Current fps based on latest measurments float currentNowFPS = (float)MSECS_PER_SECOND / _nowRenderTime; float currentSmoothFPS = (float)MSECS_PER_SECOND / _smoothRenderTime; From 727bd6b05af29d3edd9b3d23a34428418e553712 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 31 Aug 2018 13:31:26 -0700 Subject: [PATCH 190/744] don't bid for simulation ownership of locked things --- libraries/physics/src/EntityMotionState.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 925cfee740..2c130274bc 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -778,8 +778,10 @@ void EntityMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& ma bool EntityMotionState::shouldSendBid() const { // NOTE: this method is only ever called when the entity's simulation is NOT locally owned - return _body->isActive() && (_region == workload::Region::R1) && - glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) >= _entity->getSimulationPriority(); + return _body->isActive() + && (_region == workload::Region::R1) + && glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) >= _entity->getSimulationPriority() + && !_entity->getLocked(); } uint8_t EntityMotionState::computeFinalBidPriority() const { From 028092f80398a1127da3bf019e59675b5192566c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 31 Aug 2018 13:33:45 -0700 Subject: [PATCH 191/744] fix blendshapes one last time --- libraries/render-utils/src/CauterizedModel.cpp | 4 ++-- libraries/render-utils/src/CauterizedModel.h | 2 +- libraries/render-utils/src/Model.cpp | 6 +++--- libraries/render-utils/src/Model.h | 2 +- libraries/render-utils/src/SoftAttachmentModel.cpp | 4 ++-- libraries/render-utils/src/SoftAttachmentModel.h | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 259036f500..2754697db7 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -102,7 +102,7 @@ void CauterizedModel::createRenderItemSet() { } } -void CauterizedModel::updateClusterMatrices() { +void CauterizedModel::updateClusterMatrices(bool triggerBlendshapes) { PerformanceTimer perfTimer("CauterizedModel::updateClusterMatrices"); if (!_needsUpdateClusterMatrices || !isLoaded()) { @@ -175,7 +175,7 @@ void CauterizedModel::updateClusterMatrices() { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); - if (modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (triggerBlendshapes && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } diff --git a/libraries/render-utils/src/CauterizedModel.h b/libraries/render-utils/src/CauterizedModel.h index 36a96fb006..12cf921e5b 100644 --- a/libraries/render-utils/src/CauterizedModel.h +++ b/libraries/render-utils/src/CauterizedModel.h @@ -33,7 +33,7 @@ public: void createRenderItemSet() override; - virtual void updateClusterMatrices() override; + virtual void updateClusterMatrices(bool triggerBlendshapes = true) override; void updateRenderItems() override; const Model::MeshState& getCauterizeMeshState(int index) const; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 4158b14223..8287b9cca6 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -976,7 +976,7 @@ bool Model::addToScene(const render::ScenePointer& scene, render::Transaction& transaction, render::Item::Status::Getters& statusGetters) { if (!_addedToScene && isLoaded()) { - updateClusterMatrices(); + updateClusterMatrices(false); if (_modelMeshRenderItems.empty()) { createRenderItemSet(); } @@ -1486,7 +1486,7 @@ void Model::computeMeshPartLocalBounds() { } // virtual -void Model::updateClusterMatrices() { +void Model::updateClusterMatrices(bool triggerBlendshapes) { DETAILED_PERFORMANCE_TIMER("Model::updateClusterMatrices"); if (!_needsUpdateClusterMatrices || !isLoaded()) { @@ -1515,7 +1515,7 @@ void Model::updateClusterMatrices() { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); - if (modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (triggerBlendshapes && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 4642515294..5ab50da162 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -159,7 +159,7 @@ public: bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } virtual void simulate(float deltaTime, bool fullUpdate = true); - virtual void updateClusterMatrices(); + virtual void updateClusterMatrices(bool triggerBlendshapes = true); /// Returns a reference to the shared geometry. const Geometry::Pointer& getGeometry() const { return _renderGeometry; } diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index f9b90f38ba..93ce8f595a 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -31,7 +31,7 @@ int SoftAttachmentModel::getJointIndexOverride(int i) const { // virtual // use the _rigOverride matrices instead of the Model::_rig -void SoftAttachmentModel::updateClusterMatrices() { +void SoftAttachmentModel::updateClusterMatrices(bool triggerBlendshapes) { if (!_needsUpdateClusterMatrices) { return; } @@ -78,7 +78,7 @@ void SoftAttachmentModel::updateClusterMatrices() { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); - if (modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (triggerBlendshapes && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } diff --git a/libraries/render-utils/src/SoftAttachmentModel.h b/libraries/render-utils/src/SoftAttachmentModel.h index 4335c1634e..03038c35f3 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.h +++ b/libraries/render-utils/src/SoftAttachmentModel.h @@ -27,7 +27,7 @@ public: ~SoftAttachmentModel(); void updateRig(float deltaTime, glm::mat4 parentTransform) override; - void updateClusterMatrices() override; + void updateClusterMatrices(bool triggerBlendshapes = true) override; protected: int getJointIndexOverride(int i) const; From 5067047e52b169a42ffb373fb764561ffa012052 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Fri, 31 Aug 2018 14:24:52 -0700 Subject: [PATCH 192/744] warnings on ubuntu --- interface/src/LODManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 1947374b1f..2b5ca9ae8c 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -160,7 +160,7 @@ float LODManager::getLODAngleHalfTan() const { return getPerspectiveAccuracyAngleTan(_octreeSizeScale, _boundaryLevelAdjust); } float LODManager::getLODAngle() const { - return 2.0f * atan(getLODAngleHalfTan()); + return 2.0f * atanf(getLODAngleHalfTan()); } float LODManager::getLODAngleDeg() const { return glm::degrees(getLODAngle()); @@ -367,14 +367,14 @@ float LODManager::getWorldDetailQuality() const { bool inHMD = qApp->isHMDMode(); - float targetFPS = 0; + float targetFPS = 0.0f; if (inHMD) { targetFPS = getHMDLODTargetFPS(); } else { targetFPS = getDesktopLODTargetFPS(); } float maxFPS = inHMD ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS; - float percentage = 1.0 - targetFPS / maxFPS; + float percentage = 1.0f - targetFPS / maxFPS; if (percentage <= LOW) { return LOW; From e1d51a6c425de1f91818bd98901665fcc64aeb8e Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 31 Aug 2018 15:06:33 -0700 Subject: [PATCH 193/744] Avatar serializing: use memcpy where possible, take copy of joint data to avoid holding lock, etc --- libraries/avatars/src/AvatarData.cpp | 145 ++++++++++----------------- 1 file changed, 51 insertions(+), 94 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 262bf2a567..ca891eb246 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -363,13 +363,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); destinationBuffer += sizeof(packetStateFlags); +#define AVATAR_MEMCPY(src) \ + memcpy(destinationBuffer, &(src), sizeof(src)); \ + destinationBuffer += sizeof(src); + if (hasAvatarGlobalPosition) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - data->globalPosition[0] = _globalPosition.x; - data->globalPosition[1] = _globalPosition.y; - data->globalPosition[2] = _globalPosition.z; - destinationBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); + AVATAR_MEMCPY(_globalPosition); int numBytes = destinationBuffer - startSection; @@ -380,17 +380,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarBoundingBox) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - - data->avatarDimensions[0] = _globalBoundingBoxDimensions.x; - data->avatarDimensions[1] = _globalBoundingBoxDimensions.y; - data->avatarDimensions[2] = _globalBoundingBoxDimensions.z; - - data->boundOriginOffset[0] = _globalBoundingBoxOffset.x; - data->boundOriginOffset[1] = _globalBoundingBoxOffset.y; - data->boundOriginOffset[2] = _globalBoundingBoxOffset.z; - - destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); + AVATAR_MEMCPY(_globalBoundingBoxDimensions); + AVATAR_MEMCPY(_globalBoundingBoxOffset); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { @@ -424,13 +415,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasLookAtPosition) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - auto lookAt = _headData->getLookAtPosition(); - data->lookAtPosition[0] = lookAt.x; - data->lookAtPosition[1] = lookAt.y; - data->lookAtPosition[2] = lookAt.z; - destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition); - + AVATAR_MEMCPY(_headData->getLookAtPosition()); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { outboundDataRateOut->lookAtPositionRate.increment(numBytes); @@ -531,12 +516,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarLocalPosition) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - auto localPosition = getLocalPosition(); - data->localPosition[0] = localPosition.x; - data->localPosition[1] = localPosition.y; - data->localPosition[2] = localPosition.z; - destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition); + AVATAR_MEMCPY(getLocalPosition()); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { @@ -572,14 +552,17 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent auto startSection = destinationBuffer; QReadLocker readLock(&_jointDataLock); + const QVector jointData(_jointData); + readLock.unlock(); // Unlock quickly. + // joint rotation data - int numJoints = _jointData.size(); + int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; unsigned char* validityPosition = destinationBuffer; unsigned char validity = 0; int validityBit = 0; - int numValidityBytes = (int)std::ceil(numJoints / (float)BITS_IN_BYTE); + int numValidityBytes = (numJoints + BITS_IN_BYTE - 1) / BITS_IN_BYTE; #ifdef WANT_DEBUG int rotationSentCount = 0; @@ -595,24 +578,19 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent localSentJointDataOut.resize(numJoints); // Make sure the destination is resized before using it } - float minRotationDOT = !distanceAdjust ? AVATAR_MIN_ROTATION_DOT : getDistanceBasedMinRotationDOT(viewerPosition); + float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT; - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; + for (int i = 0; i < jointData.size(); i++) { + const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; if (!data.rotationIsDefaultPose) { bool mustSend = sendAll || last.rotationIsDefaultPose; if (mustSend || last.rotation != data.rotation) { - bool largeEnoughRotation = true; - if (cullSmallChanges) { - // The dot product for smaller rotations is a smaller number. - // So if the dot() is less than the value, then the rotation is a larger angle of rotation - largeEnoughRotation = fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT; - } - - if (mustSend || !cullSmallChanges || largeEnoughRotation) { + // The dot product for larger rotations is a lower number. + // So if the dot() is less than the value, then the rotation is a larger angle of rotation + if (!cullSmallChanges || fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT) { validity |= (1 << validityBit); #ifdef WANT_DEBUG rotationSentCount++; @@ -647,17 +625,17 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += numValidityBytes; // Move pointer past the validity bytes - float minTranslation = !distanceAdjust ? AVATAR_MIN_TRANSLATION : getDistanceBasedMinTranslationDistance(viewerPosition); + float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_ROTATION_DOT; float maxTranslationDimension = 0.0; - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; + for (int i = 0; i < jointData.size(); i++) { + const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; if (!data.translationIsDefaultPose) { bool mustSend = sendAll || last.translationIsDefaultPose; if (mustSend || last.translation != data.translation) { - if (mustSend || !cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { + if (!cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { validity |= (1 << validityBit); #ifdef WANT_DEBUG translationSentCount++; @@ -707,34 +685,12 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent glm::vec3 mouseFarGrabPosition = extractTranslation(mouseFarGrabMatrix); glm::quat mouseFarGrabRotation = extractRotation(mouseFarGrabMatrix); - data->leftFarGrabPosition[0] = leftFarGrabPosition.x; - data->leftFarGrabPosition[1] = leftFarGrabPosition.y; - data->leftFarGrabPosition[2] = leftFarGrabPosition.z; - - data->leftFarGrabRotation[0] = leftFarGrabRotation.w; - data->leftFarGrabRotation[1] = leftFarGrabRotation.x; - data->leftFarGrabRotation[2] = leftFarGrabRotation.y; - data->leftFarGrabRotation[3] = leftFarGrabRotation.z; - - data->rightFarGrabPosition[0] = rightFarGrabPosition.x; - data->rightFarGrabPosition[1] = rightFarGrabPosition.y; - data->rightFarGrabPosition[2] = rightFarGrabPosition.z; - - data->rightFarGrabRotation[0] = rightFarGrabRotation.w; - data->rightFarGrabRotation[1] = rightFarGrabRotation.x; - data->rightFarGrabRotation[2] = rightFarGrabRotation.y; - data->rightFarGrabRotation[3] = rightFarGrabRotation.z; - - data->mouseFarGrabPosition[0] = mouseFarGrabPosition.x; - data->mouseFarGrabPosition[1] = mouseFarGrabPosition.y; - data->mouseFarGrabPosition[2] = mouseFarGrabPosition.z; - - data->mouseFarGrabRotation[0] = mouseFarGrabRotation.w; - data->mouseFarGrabRotation[1] = mouseFarGrabRotation.x; - data->mouseFarGrabRotation[2] = mouseFarGrabRotation.y; - data->mouseFarGrabRotation[3] = mouseFarGrabRotation.z; - - destinationBuffer += sizeof(AvatarDataPacket::FarGrabJoints); + AVATAR_MEMCPY(leftFarGrabPosition); + AVATAR_MEMCPY(leftFarGrabRotation); + AVATAR_MEMCPY(rightFarGrabPosition); + AVATAR_MEMCPY(rightFarGrabRotation); + AVATAR_MEMCPY(mouseFarGrabPosition); + AVATAR_MEMCPY(mouseFarGrabRotation); int numBytes = destinationBuffer - startSection; @@ -764,8 +720,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (sentJointDataOut) { // Mark default poses in lastSentJointData, so when they become non-default we send them. - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; + for (int i = 0; i < jointData.size(); i++) { + const JointData& data = jointData[i]; JointData& local = localSentJointDataOut[i]; if (data.rotationIsDefaultPose) { local.rotationIsDefaultPose = true; @@ -778,30 +734,31 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // Push new sent joint data to sentJointDataOut sentJointDataOut->swap(localSentJointDataOut); } - } - if (hasJointDefaultPoseFlags) { - auto startSection = destinationBuffer; - QReadLocker readLock(&_jointDataLock); + // Always true, currently: + if (hasJointDefaultPoseFlags) { + auto startSection = destinationBuffer; - // write numJoints - int numJoints = _jointData.size(); - *destinationBuffer++ = (uint8_t)numJoints; + // write numJoints + int numJoints = jointData.size(); + *destinationBuffer++ = (uint8_t)numJoints; - // write rotationIsDefaultPose bits - destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { - return _jointData[i].rotationIsDefaultPose; - }); + // write rotationIsDefaultPose bits + destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { + return jointData[i].rotationIsDefaultPose; + }); - // write translationIsDefaultPose bits - destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { - return _jointData[i].translationIsDefaultPose; - }); + // write translationIsDefaultPose bits + destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { + return jointData[i].translationIsDefaultPose; + }); - if (outboundDataRateOut) { - size_t numBytes = destinationBuffer - startSection; - outboundDataRateOut->jointDefaultPoseFlagsRate.increment(numBytes); + if (outboundDataRateOut) { + size_t numBytes = destinationBuffer - startSection; + outboundDataRateOut->jointDefaultPoseFlagsRate.increment(numBytes); + } } + } int avatarDataSize = destinationBuffer - startPosition; From 2da533857475223df5f2e2c4159a01588ba8c406 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 31 Aug 2018 15:24:51 -0700 Subject: [PATCH 194/744] Fix couple of gcc issues --- libraries/avatars/src/AvatarData.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index ca891eb246..4363607e71 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -516,7 +516,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarLocalPosition) { auto startSection = destinationBuffer; - AVATAR_MEMCPY(getLocalPosition()); + const auto localPosition = getLocalPosition(); + AVATAR_MEMCPY(localPosition); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { @@ -677,7 +678,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasGrabJoints) { // the far-grab joints may range further than 3 meters, so we can't use packFloatVec3ToSignedTwoByteFixed etc auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); glm::vec3 leftFarGrabPosition = extractTranslation(leftFarGrabMatrix); glm::quat leftFarGrabRotation = extractRotation(leftFarGrabMatrix); glm::vec3 rightFarGrabPosition = extractTranslation(rightFarGrabMatrix); From 5a0de0f103cda82906cd1d47aa38b190b9d3c470 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 31 Aug 2018 16:45:39 -0700 Subject: [PATCH 195/744] Small fixes for joint logic --- libraries/avatars/src/AvatarData.cpp | 50 +++++++++++++--------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 4363607e71..c7a506a782 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -586,22 +586,19 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const JointData& last = lastSentJointData[i]; if (!data.rotationIsDefaultPose) { - bool mustSend = sendAll || last.rotationIsDefaultPose; - if (mustSend || last.rotation != data.rotation) { - - // The dot product for larger rotations is a lower number. - // So if the dot() is less than the value, then the rotation is a larger angle of rotation - if (!cullSmallChanges || fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT) { - validity |= (1 << validityBit); + // The dot product for larger rotations is a lower number. + // So if the dot() is less than the value, then the rotation is a larger angle of rotation + if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation) + || (cullSmallChanges && glm::dot(last.rotation, data.rotation) < minRotationDOT) ) { + validity |= (1 << validityBit); #ifdef WANT_DEBUG - rotationSentCount++; + rotationSentCount++; #endif - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); - if (sentJointDataOut) { - localSentJointDataOut[i].rotation = data.rotation; - localSentJointDataOut[i].rotationIsDefaultPose = false; - } + if (sentJointDataOut) { + localSentJointDataOut[i].rotation = data.rotation; + localSentJointDataOut[i].rotationIsDefaultPose = false; } } } @@ -634,24 +631,23 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const JointData& last = lastSentJointData[i]; if (!data.translationIsDefaultPose) { - bool mustSend = sendAll || last.translationIsDefaultPose; - if (mustSend || last.translation != data.translation) { - if (!cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { - validity |= (1 << validityBit); + if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) + || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { + + validity |= (1 << validityBit); #ifdef WANT_DEBUG - translationSentCount++; + translationSentCount++; #endif - maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); - destinationBuffer += - packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + destinationBuffer += + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); - if (sentJointDataOut) { - localSentJointDataOut[i].translation = data.translation; - localSentJointDataOut[i].translationIsDefaultPose = false; - } + if (sentJointDataOut) { + localSentJointDataOut[i].translation = data.translation; + localSentJointDataOut[i].translationIsDefaultPose = false; } } } From 48680329ec4b29076179eeb04c882408d575d53f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 29 Aug 2018 21:26:19 -0700 Subject: [PATCH 196/744] mark trait instances needing to be sent on mixer reconnect --- libraries/avatars/src/AvatarData.cpp | 12 +++++++++++- libraries/avatars/src/AvatarData.h | 2 ++ libraries/avatars/src/ClientTraitsHandler.cpp | 6 ++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index b86398501e..ea6dbd7074 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1895,6 +1895,16 @@ qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTr return bytesWritten; } +void AvatarData::prepareResetTraitInstances() { + if (_clientTraitsHandler) { + _avatarEntitiesLock.withReadLock([this]{ + foreach (auto entityID, _avatarEntityData.keys()) { + _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); + } + }); + } +} + void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) { if (traitType == AvatarTraits::SkeletonModelURL) { // get the URL from the binary data @@ -2792,7 +2802,7 @@ void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { if (_clientTraitsHandler) { // if we have a client traits handler, flag any updated or created entities // so that we send changes for them next frame - foreach (auto entityID, _avatarEntityData) { + foreach (auto entityID, _avatarEntityData.keys()) { _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); } } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 0474d07acd..db2b82b0b7 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -964,6 +964,8 @@ public: qint64 packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); + void prepareResetTraitInstances(); + void processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData); void processTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData); diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index c4073cb86a..a06b53da7c 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -37,6 +37,12 @@ void ClientTraitsHandler::resetForNewMixer() { // mark that all traits should be sent next time _shouldPerformInitialSend = true; + + // reset the trait statuses + _traitStatuses.reset(); + + // pre-fill the instanced statuses that we will need to send next frame + _owningAvatar->prepareResetTraitInstances(); } void ClientTraitsHandler::sendChangedTraitsToMixer() { From 20912349a4f0b8854bda467ee1e1bd04d9bb67ef Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 30 Aug 2018 12:59:28 -0700 Subject: [PATCH 197/744] cycle avatar entity IDs for new avatar mixer --- interface/src/Application.cpp | 1 - .../src/avatars-renderer/Avatar.cpp | 1 - libraries/avatars/src/AvatarData.cpp | 10 ++++++++-- libraries/avatars/src/AvatarData.h | 3 ++- libraries/avatars/src/AvatarTraits.h | 2 +- libraries/avatars/src/ClientTraitsHandler.cpp | 20 ++++++++++++++++++- libraries/avatars/src/ClientTraitsHandler.h | 8 ++++++-- 7 files changed, 36 insertions(+), 9 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6e57311306..2a7abfa2da 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6366,7 +6366,6 @@ void Application::clearDomainOctreeDetails() { } void Application::clearDomainAvatars() { - getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities DependencyManager::get()->clearOtherAvatars(); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index a9af3b7725..4ffccefe61 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -139,7 +139,6 @@ Avatar::~Avatar() { } }); } - auto geometryCache = DependencyManager::get(); if (geometryCache) { geometryCache->releaseID(_nameRectGeometryID); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index ea6dbd7074..e7aaa18576 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1861,7 +1861,9 @@ qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice } qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID, - ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) { + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion, + AvatarTraits::TraitInstanceID wireInstanceID) { + qint64 bytesWritten = 0; bytesWritten += destination.writePrimitive(traitType); @@ -1870,7 +1872,11 @@ qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTr bytesWritten += destination.writePrimitive(traitVersion); } - bytesWritten += destination.write(traitInstanceID.toRfc4122()); + if (!wireInstanceID.isNull()) { + bytesWritten += destination.write(wireInstanceID.toRfc4122()); + } else { + bytesWritten += destination.write(traitInstanceID.toRfc4122()); + } if (traitType == AvatarTraits::AvatarEntity) { // grab a read lock on the avatar entities and check for entity data for the given ID diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index db2b82b0b7..b010a9f924 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -962,7 +962,8 @@ public: qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); qint64 packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, - ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION, + AvatarTraits::TraitInstanceID wireInstanceID = AvatarTraits::TraitInstanceID()); void prepareResetTraitInstances(); diff --git a/libraries/avatars/src/AvatarTraits.h b/libraries/avatars/src/AvatarTraits.h index f0c807a432..5866caf234 100644 --- a/libraries/avatars/src/AvatarTraits.h +++ b/libraries/avatars/src/AvatarTraits.h @@ -41,7 +41,7 @@ namespace AvatarTraits { const TraitWireSize DELETED_TRAIT_SIZE = -1; inline qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination, - TraitVersion traitVersion = NULL_TRAIT_VERSION) { + TraitVersion traitVersion = NULL_TRAIT_VERSION) { qint64 bytesWritten = 0; bytesWritten += destination.writePrimitive(traitType); diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index a06b53da7c..94823610dc 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -43,6 +43,9 @@ void ClientTraitsHandler::resetForNewMixer() { // pre-fill the instanced statuses that we will need to send next frame _owningAvatar->prepareResetTraitInstances(); + + // reset the trait XOR ID since we're resetting for a new avatar mixer + _sessionXORID = QUuid::createUuid().toRfc4122(); } void ClientTraitsHandler::sendChangedTraitsToMixer() { @@ -93,7 +96,11 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { || instanceIDValuePair.value == Updated) { // this is a changed trait we need to send or we haven't send out trait information yet // ask the owning avatar to pack it - _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList); + + // since this is going to the mixer, use the XORed instance ID (to anonymize trait instance IDs + // that would typically persist across sessions) + _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList, + AvatarTraits::NULL_TRAIT_VERSION, xorInstanceID(instanceIDValuePair.id)); } else if (!_shouldPerformInitialSend && instanceIDValuePair.value == Deleted) { // pack delete for this trait instance AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id, @@ -111,6 +118,17 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { } } +AvatarTraits::TraitInstanceID ClientTraitsHandler::xorInstanceID(AvatarTraits::TraitInstanceID localInstanceID) { + QByteArray xorInstanceID { NUM_BYTES_RFC4122_UUID, 0 }; + auto localInstanceIDBytes = localInstanceID.toRfc4122(); + + for (auto i = 0; i < localInstanceIDBytes.size(); ++i) { + xorInstanceID[i] = localInstanceIDBytes[i] ^ _sessionXORID[i]; + } + + return QUuid::fromRfc4122(xorInstanceID); +} + void ClientTraitsHandler::processTraitOverride(QSharedPointer message, SharedNodePointer sendingNode) { if (sendingNode->getType() == NodeType::AvatarMixer) { while (message->getBytesLeftToRead()) { diff --git a/libraries/avatars/src/ClientTraitsHandler.h b/libraries/avatars/src/ClientTraitsHandler.h index 27ba58d46b..2319061502 100644 --- a/libraries/avatars/src/ClientTraitsHandler.h +++ b/libraries/avatars/src/ClientTraitsHandler.h @@ -30,13 +30,15 @@ public: void markTraitUpdated(AvatarTraits::TraitType updatedTrait) { _traitStatuses[updatedTrait] = Updated; _hasChangedTraits = true; } - void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID) + void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID updatedInstanceID) { _traitStatuses.instanceInsert(traitType, updatedInstanceID, Updated); _hasChangedTraits = true; } - void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID) + void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID deleteInstanceID) { _traitStatuses.instanceInsert(traitType, deleteInstanceID, Deleted); _hasChangedTraits = true; } void resetForNewMixer(); + AvatarTraits::TraitInstanceID xorInstanceID(AvatarTraits::TraitInstanceID localInstanceID); + public slots: void processTraitOverride(QSharedPointer message, SharedNodePointer sendingNode); @@ -53,6 +55,8 @@ private: AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION }; AvatarTraits::TraitVersion _currentSkeletonVersion { AvatarTraits::NULL_TRAIT_VERSION }; + + QByteArray _sessionXORID { NUM_BYTES_RFC4122_UUID, 0}; bool _shouldPerformInitialSend { false }; bool _hasChangedTraits { false }; From 9b3d9dd0f319ad2964e54a72bd17934765a91c02 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 31 Aug 2018 16:31:33 -0700 Subject: [PATCH 198/744] XOR incoming trait instance IDs for other avatars --- libraries/avatars/src/AvatarData.h | 5 +++++ libraries/avatars/src/AvatarHashMap.cpp | 20 ++++++++++++++---- libraries/avatars/src/AvatarTraits.h | 21 +++++++++++++++++-- libraries/avatars/src/ClientTraitsHandler.cpp | 21 +++++++------------ libraries/avatars/src/ClientTraitsHandler.h | 4 ---- 5 files changed, 47 insertions(+), 24 deletions(-) diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index b010a9f924..e9f1f5f6c3 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1193,6 +1193,9 @@ public: void setReplicaIndex(int replicaIndex) { _replicaIndex = replicaIndex; } int getReplicaIndex() { return _replicaIndex; } + const AvatarTraits::TraitInstanceID getTraitInstanceXORID() const { return _traitInstanceXORID; } + void cycleTraitInstanceXORID() { _traitInstanceXORID = QUuid::createUuid(); } + signals: /**jsdoc @@ -1499,6 +1502,8 @@ private: // privatize the copy constructor and assignment operator so they cannot be called AvatarData(const AvatarData&); AvatarData& operator= (const AvatarData&); + + AvatarTraits::TraitInstanceID _traitInstanceXORID { QUuid::createUuid() }; }; Q_DECLARE_METATYPE(AvatarData*) diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index c437b56f32..fac2af9ebc 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -335,17 +335,29 @@ void AvatarHashMap::processBulkAvatarTraits(QSharedPointer mess AvatarTraits::TraitInstanceID traitInstanceID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + // XOR the incoming trait instance ID with this avatar object's personal XOR ID + + // this ensures that we have separate entity instances in the local tree + // if we briefly end up with two Avatar objects for this node + + // (which can occur if the shared pointer for the + // previous instance of an avatar hasn't yet gone out of scope before the + // new instance is created) + + auto xoredInstanceID = AvatarTraits::xoredInstanceID(traitInstanceID, avatar->getTraitInstanceXORID()); + message->readPrimitive(&traitBinarySize); auto& processedInstanceVersion = lastProcessedVersions.getInstanceValueRef(traitType, traitInstanceID); if (packetTraitVersion > processedInstanceVersion) { + // in order to handle re-connections to the avatar mixer when the other if (traitBinarySize == AvatarTraits::DELETED_TRAIT_SIZE) { - avatar->processDeletedTraitInstance(traitType, traitInstanceID); - _replicas.processDeletedTraitInstance(avatarID, traitType, traitInstanceID); + avatar->processDeletedTraitInstance(traitType, xoredInstanceID); + _replicas.processDeletedTraitInstance(avatarID, traitType, xoredInstanceID); } else { auto traitData = message->read(traitBinarySize); - avatar->processTraitInstance(traitType, traitInstanceID, traitData); - _replicas.processTraitInstance(avatarID, traitType, traitInstanceID, traitData); + avatar->processTraitInstance(traitType, xoredInstanceID, traitData); + _replicas.processTraitInstance(avatarID, traitType, xoredInstanceID, traitData); } processedInstanceVersion = packetTraitVersion; } else { diff --git a/libraries/avatars/src/AvatarTraits.h b/libraries/avatars/src/AvatarTraits.h index 5866caf234..47be0d6111 100644 --- a/libraries/avatars/src/AvatarTraits.h +++ b/libraries/avatars/src/AvatarTraits.h @@ -41,7 +41,8 @@ namespace AvatarTraits { const TraitWireSize DELETED_TRAIT_SIZE = -1; inline qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination, - TraitVersion traitVersion = NULL_TRAIT_VERSION) { + TraitVersion traitVersion = NULL_TRAIT_VERSION, + TraitInstanceID xoredInstanceID = TraitInstanceID()) { qint64 bytesWritten = 0; bytesWritten += destination.writePrimitive(traitType); @@ -50,12 +51,28 @@ namespace AvatarTraits { bytesWritten += destination.writePrimitive(traitVersion); } - bytesWritten += destination.write(instanceID.toRfc4122()); + if (xoredInstanceID.isNull()) { + bytesWritten += destination.write(instanceID.toRfc4122()); + } else { + bytesWritten += destination.write(xoredInstanceID.toRfc4122()); + } bytesWritten += destination.writePrimitive(DELETED_TRAIT_SIZE); return bytesWritten; } + + inline TraitInstanceID xoredInstanceID(TraitInstanceID localInstanceID, TraitInstanceID xorKeyID) { + QByteArray xoredInstanceID { NUM_BYTES_RFC4122_UUID, 0 }; + auto xorKeyIDBytes = xorKeyID.toRfc4122(); + auto localInstanceIDBytes = localInstanceID.toRfc4122(); + + for (auto i = 0; i < localInstanceIDBytes.size(); ++i) { + xoredInstanceID[i] = localInstanceIDBytes[i] ^ xorKeyIDBytes[i]; + } + + return QUuid::fromRfc4122(xoredInstanceID); + } }; #endif // hifi_AvatarTraits_h diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index 94823610dc..479852cf9a 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -45,7 +45,7 @@ void ClientTraitsHandler::resetForNewMixer() { _owningAvatar->prepareResetTraitInstances(); // reset the trait XOR ID since we're resetting for a new avatar mixer - _sessionXORID = QUuid::createUuid().toRfc4122(); + _owningAvatar->cycleTraitInstanceXORID(); } void ClientTraitsHandler::sendChangedTraitsToMixer() { @@ -100,11 +100,15 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { // since this is going to the mixer, use the XORed instance ID (to anonymize trait instance IDs // that would typically persist across sessions) _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList, - AvatarTraits::NULL_TRAIT_VERSION, xorInstanceID(instanceIDValuePair.id)); + AvatarTraits::NULL_TRAIT_VERSION, + AvatarTraits::xoredInstanceID(instanceIDValuePair.id, + _owningAvatar->getTraitInstanceXORID())); } else if (!_shouldPerformInitialSend && instanceIDValuePair.value == Deleted) { // pack delete for this trait instance AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id, - *traitsPacketList); + *traitsPacketList, AvatarTraits::NULL_TRAIT_VERSION, + AvatarTraits::xoredInstanceID(instanceIDValuePair.id, + _owningAvatar->getTraitInstanceXORID())); } } @@ -118,17 +122,6 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { } } -AvatarTraits::TraitInstanceID ClientTraitsHandler::xorInstanceID(AvatarTraits::TraitInstanceID localInstanceID) { - QByteArray xorInstanceID { NUM_BYTES_RFC4122_UUID, 0 }; - auto localInstanceIDBytes = localInstanceID.toRfc4122(); - - for (auto i = 0; i < localInstanceIDBytes.size(); ++i) { - xorInstanceID[i] = localInstanceIDBytes[i] ^ _sessionXORID[i]; - } - - return QUuid::fromRfc4122(xorInstanceID); -} - void ClientTraitsHandler::processTraitOverride(QSharedPointer message, SharedNodePointer sendingNode) { if (sendingNode->getType() == NodeType::AvatarMixer) { while (message->getBytesLeftToRead()) { diff --git a/libraries/avatars/src/ClientTraitsHandler.h b/libraries/avatars/src/ClientTraitsHandler.h index 2319061502..6d1592ba74 100644 --- a/libraries/avatars/src/ClientTraitsHandler.h +++ b/libraries/avatars/src/ClientTraitsHandler.h @@ -37,8 +37,6 @@ public: void resetForNewMixer(); - AvatarTraits::TraitInstanceID xorInstanceID(AvatarTraits::TraitInstanceID localInstanceID); - public slots: void processTraitOverride(QSharedPointer message, SharedNodePointer sendingNode); @@ -55,8 +53,6 @@ private: AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION }; AvatarTraits::TraitVersion _currentSkeletonVersion { AvatarTraits::NULL_TRAIT_VERSION }; - - QByteArray _sessionXORID { NUM_BYTES_RFC4122_UUID, 0}; bool _shouldPerformInitialSend { false }; bool _hasChangedTraits { false }; From a37a19da1e4e7657548fb29ad6cf4e130a5eda76 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 31 Aug 2018 16:59:24 -0700 Subject: [PATCH 199/744] use different XORed instance ID for replicas --- libraries/avatars/src/AvatarHashMap.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index fac2af9ebc..c1246866dc 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -85,7 +85,8 @@ void AvatarReplicas::processDeletedTraitInstance(const QUuid& parentID, AvatarTr if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; for (auto avatar : replicas) { - avatar->processDeletedTraitInstance(traitType, instanceID); + avatar->processDeletedTraitInstance(traitType, + AvatarTraits::xoredInstanceID(instanceID, avatar->getTraitInstanceXORID())); } } } @@ -94,7 +95,9 @@ void AvatarReplicas::processTraitInstance(const QUuid& parentID, AvatarTraits::T if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; for (auto avatar : replicas) { - avatar->processTraitInstance(traitType, instanceID, traitBinaryData); + avatar->processTraitInstance(traitType, + AvatarTraits::xoredInstanceID(instanceID, avatar->getTraitInstanceXORID()), + traitBinaryData); } } } @@ -353,11 +356,11 @@ void AvatarHashMap::processBulkAvatarTraits(QSharedPointer mess // in order to handle re-connections to the avatar mixer when the other if (traitBinarySize == AvatarTraits::DELETED_TRAIT_SIZE) { avatar->processDeletedTraitInstance(traitType, xoredInstanceID); - _replicas.processDeletedTraitInstance(avatarID, traitType, xoredInstanceID); + _replicas.processDeletedTraitInstance(avatarID, traitType, traitInstanceID); } else { auto traitData = message->read(traitBinarySize); avatar->processTraitInstance(traitType, xoredInstanceID, traitData); - _replicas.processTraitInstance(avatarID, traitType, xoredInstanceID, traitData); + _replicas.processTraitInstance(avatarID, traitType, traitInstanceID, traitData); } processedInstanceVersion = packetTraitVersion; } else { From cf5f81ab95da956565f1d6035f1e38cec12bc48d Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 31 Aug 2018 17:04:23 -0700 Subject: [PATCH 200/744] Don't memcpy quaternions as we don't use same component order on wire! --- libraries/avatars/src/AvatarData.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index c7a506a782..d60a1a1310 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -666,6 +666,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation()); destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), TRANSLATION_COMPRESSION_RADIX); + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation()); destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), @@ -674,6 +675,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasGrabJoints) { // the far-grab joints may range further than 3 meters, so we can't use packFloatVec3ToSignedTwoByteFixed etc auto startSection = destinationBuffer; + auto data = reinterpret_cast(destinationBuffer); glm::vec3 leftFarGrabPosition = extractTranslation(leftFarGrabMatrix); glm::quat leftFarGrabRotation = extractRotation(leftFarGrabMatrix); glm::vec3 rightFarGrabPosition = extractTranslation(rightFarGrabMatrix); @@ -682,11 +684,25 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent glm::quat mouseFarGrabRotation = extractRotation(mouseFarGrabMatrix); AVATAR_MEMCPY(leftFarGrabPosition); - AVATAR_MEMCPY(leftFarGrabRotation); + data->leftFarGrabRotation[0] = leftFarGrabRotation.w; + data->leftFarGrabRotation[1] = leftFarGrabRotation.x; + data->leftFarGrabRotation[2] = leftFarGrabRotation.y; + data->leftFarGrabRotation[3] = leftFarGrabRotation.z; + destinationBuffer += sizeof(data->leftFarGrabPosition); + AVATAR_MEMCPY(rightFarGrabPosition); - AVATAR_MEMCPY(rightFarGrabRotation); + data->rightFarGrabRotation[0] = rightFarGrabRotation.w; + data->rightFarGrabRotation[1] = rightFarGrabRotation.x; + data->rightFarGrabRotation[2] = rightFarGrabRotation.y; + data->rightFarGrabRotation[3] = rightFarGrabRotation.z; + destinationBuffer += sizeof(data->rightFarGrabRotation); + AVATAR_MEMCPY(mouseFarGrabPosition); - AVATAR_MEMCPY(mouseFarGrabRotation); + data->mouseFarGrabRotation[0] = mouseFarGrabRotation.w; + data->mouseFarGrabRotation[1] = mouseFarGrabRotation.x; + data->mouseFarGrabRotation[2] = mouseFarGrabRotation.y; + data->mouseFarGrabRotation[3] = mouseFarGrabRotation.z; + destinationBuffer += sizeof(data->mouseFarGrabRotation); int numBytes = destinationBuffer - startSection; From 11a563cb386290c254040e4a5a0f1a79326af3e2 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 31 Aug 2018 17:40:57 -0700 Subject: [PATCH 201/744] Don't use temp vector for outgoing joint status - doesn't seem to be necessary --- libraries/avatars/src/AvatarData.cpp | 34 ++++++++-------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index d60a1a1310..ed1a8e5742 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -573,10 +573,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += numValidityBytes; // Move pointer past the validity bytes // sentJointDataOut and lastSentJointData might be the same vector - // build sentJointDataOut locally and then swap it at the end. - QVector localSentJointDataOut; if (sentJointDataOut) { - localSentJointDataOut.resize(numJoints); // Make sure the destination is resized before using it + sentJointDataOut->resize(numJoints); // Make sure the destination is resized before using it } float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT; @@ -597,11 +595,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); if (sentJointDataOut) { - localSentJointDataOut[i].rotation = data.rotation; - localSentJointDataOut[i].rotationIsDefaultPose = false; + (*sentJointDataOut)[i].rotation = data.rotation; } } } + + (*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose; + if (++validityBit == BITS_IN_BYTE) { *validityPosition++ = validity; validityBit = validity = 0; @@ -646,11 +646,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); if (sentJointDataOut) { - localSentJointDataOut[i].translation = data.translation; - localSentJointDataOut[i].translationIsDefaultPose = false; + (*sentJointDataOut)[i].translation = data.translation; } } } + + (*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose; + if (++validityBit == BITS_IN_BYTE) { *validityPosition++ = validity; validityBit = validity = 0; @@ -729,24 +731,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent outboundDataRateOut->jointDataRate.increment(numBytes); } - if (sentJointDataOut) { - - // Mark default poses in lastSentJointData, so when they become non-default we send them. - for (int i = 0; i < jointData.size(); i++) { - const JointData& data = jointData[i]; - JointData& local = localSentJointDataOut[i]; - if (data.rotationIsDefaultPose) { - local.rotationIsDefaultPose = true; - } - if (data.translationIsDefaultPose) { - local.translationIsDefaultPose = true; - } - } - - // Push new sent joint data to sentJointDataOut - sentJointDataOut->swap(localSentJointDataOut); - } - // Always true, currently: if (hasJointDefaultPoseFlags) { auto startSection = destinationBuffer; From c95cb97b9bbf29e69971a8fb3faa8dcd89f5295d Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 31 Aug 2018 19:29:48 -0700 Subject: [PATCH 202/744] adding working version with networking signals --- interface/resources/serverless/redirect.json | 1609 +++++++++--------- interface/src/Application.cpp | 31 +- interface/src/Application.h | 4 +- libraries/networking/src/AddressManager.cpp | 44 +- libraries/networking/src/AddressManager.h | 12 +- libraries/networking/src/DomainHandler.cpp | 21 +- libraries/networking/src/DomainHandler.h | 4 +- 7 files changed, 891 insertions(+), 834 deletions(-) diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index 71dfae4212..fd81b6d433 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -5,91 +5,91 @@ "clientOnly": false, "collidesWith": "static,dynamic,kinematic,otherAvatar,", "collisionMask": 23, - "created": "2018-08-29T22:59:44Z", + "created": "2018-08-31T21:40:18Z", "dimensions": { - "blue": 0.26190000772476196, - "green": 0.5595999956130981, - "red": 0.5318999886512756, - "x": 0.5318999886512756, - "y": 0.5595999956130981, - "z": 0.26190000772476196 + "blue": 0.8231174349784851, + "green": 1.8264780044555664, + "red": 1.2112245559692383, + "x": 1.2112245559692383, + "y": 1.8264780044555664, + "z": 0.8231174349784851 }, - "id": "{4782f05d-ca51-41d4-9d19-21b8cad5ec2d}", - "lastEdited": 1535584079688600, - "lastEditedBy": "{f5457c14-32ca-45c2-9dde-4f5b30b3be1b}", - "name": "Try Again Box", + "id": "{1a8bf6e0-6f03-4761-9aba-1f624fae1236}", + "lastEdited": 1535751992686833, + "lastEditedBy": "{ace8f357-ad94-49e1-b4d0-39c4d5a936f3}", + "name": "Try Again Zone", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { - "blue": 4.54052734375, - "green": 1.722428798675537, - "red": 4.593362331390381, - "x": 0.5933623313903809, - "y": -7.777571201324463, - "z": 0.54052734375 + "blue": 4.442525863647461, + "green": 1.9289360046386719, + "red": 2.600449562072754, + "x": -1.399550437927246, + "y": -7.571063995361328, + "z": 0.44252586364746094 }, "queryAACube": { - "scale": 0.8152676820755005, - "x": 4.185728549957275, - "y": 1.3147950172424316, - "z": 4.1328935623168945 + "scale": 2.3410699367523193, + "x": 1.4299145936965942, + "y": 0.7584010362625122, + "z": 3.2719907760620117 }, "rotation": { - "w": 0.9868703484535217, - "x": -0.010178769007325172, - "y": -0.15702557563781738, - "z": 0.03642075136303902 - }, - "script": "file:///C:/Users/wayne/development/tryAgainBoxEntityScript.js", - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "created": "2018-08-29T23:01:55Z", - "dimensions": { - "blue": 0.26190000772476196, - "green": 0.5595999956130981, - "red": 0.5318999886512756, - "x": 0.5318999886512756, - "y": 0.5595999956130981, - "z": 0.26190000772476196 - }, - "id": "{8c9287c8-3826-4f2e-944e-05d8d4715fb8}", - "lastEdited": 1535584095676575, - "lastEditedBy": "{f5457c14-32ca-45c2-9dde-4f5b30b3be1b}", - "name": "Back Box", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 3.9590959548950195, - "green": 1.738053798675537, - "red": 5.0181121826171875, - "x": 1.0181121826171875, - "y": -7.761946201324463, - "z": -0.04090404510498047 - }, - "queryAACube": { - "scale": 0.8152676820755005, - "x": 4.610478401184082, - "y": 1.3304200172424316, - "z": 3.551462173461914 - }, - "rotation": { - "w": 0.9366722106933594, + "w": 0.9743700623512268, "x": 0, - "y": -0.3502073884010315, + "y": -0.22495104372501373, "z": 0 }, - "script": "file:///C:/Users/wayne/development/backBoxEntityScript.js", - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":true}}", - "visible": false + "script": "file:///C:/Users/wayne/development/zoneTryAgainEntityScript.js", + "shapeType": "box", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, { "clientOnly": false, - "created": "2018-08-29T22:57:33Z", + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "created": "2018-08-31T21:44:40Z", + "dimensions": { + "blue": 0.7544999718666077, + "green": 1.8265000581741333, + "red": 1.0555000305175781, + "x": 1.0555000305175781, + "y": 1.8265000581741333, + "z": 0.7544999718666077 + }, + "id": "{eb439982-948a-4393-8e8c-fdd833e6d9a7}", + "lastEdited": 1535752005525769, + "lastEditedBy": "{ace8f357-ad94-49e1-b4d0-39c4d5a936f3}", + "name": "Back Zone", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 2.538674831390381, + "green": 1.8022732734680176, + "red": 3.5101304054260254, + "x": -0.4898695945739746, + "y": -7.697726726531982, + "z": -1.4613251686096191 + }, + "queryAACube": { + "scale": 2.2404136657714844, + "x": 2.389923572540283, + "y": 0.6820664405822754, + "z": 1.4184679985046387 + }, + "rotation": { + "w": 0.9304176568984985, + "x": 0, + "y": -0.36650121212005615, + "z": 0 + }, + "script": "file:///C:/Users/wayne/development/zoneBackEntityScript.js", + "shapeType": "box", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "created": "2018-08-31T21:35:56Z", "dimensions": { "blue": 14.40000057220459, "green": 14.40000057220459, @@ -98,9 +98,9 @@ "y": 14.40000057220459, "z": 14.40000057220459 }, - "id": "{8e6bd656-d764-4724-a89d-7b4ff96abf8a}", - "lastEdited": 1535583477860044, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "id": "{dbae0b3a-a7ff-4eb1-9177-fd1f20b3cec8}", + "lastEdited": 1535751789461074, + "lastEditedBy": "{ace8f357-ad94-49e1-b4d0-39c4d5a936f3}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { "blue": 2.3440732955932617, @@ -127,6 +127,239 @@ "type": "Zone", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{c92b026b-e13c-448a-903c-2773f9fdc2e8}", + "lastEdited": 1535751961512543, + "lastEditedBy": "{ace8f357-ad94-49e1-b4d0-39c4d5a936f3}", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.9063844680786133, + "green": 1.15850830078125, + "red": 0.16632509231567383, + "x": -3.833674907684326, + "y": -8.34149169921875, + "z": -2.0936155319213867 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -5.674418926239014, + "y": -4.6822357177734375, + "z": -3.934359550476074 + }, + "rotation": { + "w": 0.9666743278503418, + "x": -4.57763671875e-05, + "y": -0.2560006380081177, + "z": 1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{cd72debb-75a6-420d-b39f-b903fb069798}", + "lastEdited": 1535751362199714, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 6.1806135177612305, + "green": 1.1588134765625, + "red": 1.4755167961120605, + "x": -2.5244832038879395, + "y": -8.3411865234375, + "z": 2.1806135177612305 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -4.365227222442627, + "y": -4.6819305419921875, + "z": 0.33986949920654297 + }, + "rotation": { + "w": 0.8637980222702026, + "x": -4.57763671875e-05, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{abe300d5-32b8-4d0c-bf82-e78005b79b70}", + "lastEdited": 1535751362199153, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 5.268576622009277, + "green": 1.1588134765625, + "red": 6.100250244140625, + "x": 2.100250244140625, + "y": -8.3411865234375, + "z": 1.2685766220092773 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": 0.2595062255859375, + "y": -4.6819305419921875, + "z": -0.5721673965454102 + }, + "rotation": { + "w": 0.9662165641784668, + "x": -4.57763671875e-05, + "y": -0.2576791048049927, + "z": 1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{66757fda-b7ad-4858-83ab-724b01710cc2}", + "lastEdited": 1535751362198937, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.3116319179534912, + "green": 0.15618896484375, + "red": 2.705613613128662, + "x": -1.294386386871338, + "y": -9.34381103515625, + "z": -2.688368082046509 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 2.5324084758758545, + "y": -0.017016112804412842, + "z": 1.1384267807006836 + }, + "rotation": { + "w": 0.46953535079956055, + "x": -0.16719311475753784, + "y": -0.7982757091522217, + "z": 0.3380941152572632 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{24eda146-92f8-4e43-b084-fb555626427b}", + "lastEdited": 1535751362198263, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.3116319179534912, + "green": 0, + "red": 2.705613613128662, + "x": -1.294386386871338, + "y": -9.5, + "z": -2.688368082046509 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 2.5324084758758545, + "y": -0.17320507764816284, + "z": 1.1384267807006836 + }, + "rotation": { + "w": 0.9127794504165649, + "x": 0.2575265169143677, + "y": 0.15553522109985352, + "z": 0.2761729955673218 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, { "alpha": 0, "alphaFinish": 0, @@ -148,7 +381,7 @@ "y": 255, "z": 255 }, - "created": "2018-08-29T22:57:33Z", + "created": "2018-08-31T21:35:56Z", "dimensions": { "blue": 13.24000072479248, "green": 13.24000072479248, @@ -181,8 +414,419 @@ }, "emitRate": 6, "emitSpeed": 0, - "id": "{b5ba3aa4-2eb9-4cfd-aec7-43635511c6b7}", - "lastEdited": 1535583477861791, + "emitterShouldTrail": true, + "id": "{b9a46e3e-f982-4a46-b38d-2a38e5cc5fbc}", + "lastEdited": 1535751362199964, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "lifespan": 10, + "maxParticles": 10, + "name": "Stars", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.07000000029802322, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 3.78922963142395, + "green": 0.5220947265625, + "red": 1.1928560733795166, + "x": -2.8071439266204834, + "y": -8.9779052734375, + "z": -0.2107703685760498 + }, + "queryAACube": { + "scale": 22.932353973388672, + "x": -10.273321151733398, + "y": -10.944082260131836, + "z": -7.676947593688965 + }, + "radiusFinish": 0, + "radiusStart": 0, + "rotation": { + "w": 0.996429443359375, + "x": -1.52587890625e-05, + "y": -0.08442819118499756, + "z": -4.57763671875e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{e6897ca3-1c22-429f-a1b6-173ff47397a7}", + "lastEdited": 1535751362197848, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 0, + "green": 1.1583251953125, + "red": 4.971565246582031, + "x": 0.9715652465820312, + "y": -8.3416748046875, + "z": -4 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -0.8691787719726562, + "y": -4.6824188232421875, + "z": -5.8407440185546875 + }, + "rotation": { + "w": 0.8637980222702026, + "x": -4.57763671875e-05, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{16ca16ab-eee3-41b1-b583-159245aa2010}", + "lastEdited": 1535751362197055, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.3116319179534912, + "green": 0.0772705078125, + "red": 2.705613613128662, + "x": -1.294386386871338, + "y": -9.4227294921875, + "z": -2.688368082046509 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 2.5324084758758545, + "y": -0.09593456983566284, + "z": 1.1384267807006836 + }, + "rotation": { + "w": -0.38777750730514526, + "x": -0.37337303161621094, + "y": -0.8409399390220642, + "z": 0.055222392082214355 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 2.1097896099090576, + "green": 0.04847164824604988, + "red": 1.458284616470337, + "x": 1.458284616470337, + "y": 0.04847164824604988, + "z": 2.1097896099090576 + }, + "id": "{482ae172-c411-41fb-9a41-4215eef25478}", + "lastEdited": 1535751754378548, + "lastEditedBy": "{ace8f357-ad94-49e1-b4d0-39c4d5a936f3}", + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal2.fbx", + "name": "Back", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.5835940837860107, + "green": 0.2467041015625, + "red": 3.0345542430877686, + "x": -0.9654457569122314, + "y": -9.2532958984375, + "z": -2.4164059162139893 + }, + "queryAACube": { + "scale": 2.5651814937591553, + "x": 1.751963496208191, + "y": -1.0358866453170776, + "z": 0.3010033369064331 + }, + "rotation": { + "w": 0.9084458351135254, + "x": -1.52587890625e-05, + "y": 0.4179598093032837, + "z": -0.0001068115234375 + }, + "script": "file:///C:/Users/wayne/development/backEntityScript.js", + "scriptTimestamp": 1535751754379, + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 9.030570983886719, + "green": 0.0719478651881218, + "red": 9.030570983886719, + "x": 9.030570983886719, + "y": 0.0719478651881218, + "z": 9.030570983886719 + }, + "id": "{33440bf8-f9ce-4d52-9b29-1b321e226982}", + "lastEdited": 1535751362197214, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/floor.fbx", + "name": "Floor", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 4.2324604988098145, + "green": 0.17547607421875, + "red": 4.802988529205322, + "x": 0.8029885292053223, + "y": -9.32452392578125, + "z": 0.23246049880981445 + }, + "queryAACube": { + "scale": 12.771358489990234, + "x": -1.582690715789795, + "y": -6.210203170776367, + "z": -2.1532187461853027 + }, + "rotation": { + "w": 0.8648051023483276, + "x": -1.52587890625e-05, + "y": 0.5020675659179688, + "z": -4.57763671875e-05 + }, + "shapeType": "simple-hull", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{7639140e-cd6f-4f86-97f7-00a19f50253e}", + "lastEdited": 1535751362198721, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.8216662406921387, + "green": 0, + "red": 1.2409718036651611, + "x": -2.759028196334839, + "y": -9.5, + "z": -0.17833375930786133 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 1.0677666664123535, + "y": -0.17320507764816284, + "z": 3.648461103439331 + }, + "rotation": { + "w": 0.6444342136383057, + "x": -0.08220034837722778, + "y": -0.6649118661880493, + "z": 0.3684900999069214 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 2.1097896099090576, + "green": 0.04847164824604988, + "red": 1.458284616470337, + "x": 1.458284616470337, + "y": 0.04847164824604988, + "z": 2.1097896099090576 + }, + "id": "{7755bf99-b026-490d-a682-edadd3f65701}", + "lastEdited": 1535751900322535, + "lastEditedBy": "{ace8f357-ad94-49e1-b4d0-39c4d5a936f3}", + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal1.fbx", + "name": "Try Again", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.946338653564453, + "green": 0.2467041015625, + "red": 1.6013128757476807, + "x": -2.3986871242523193, + "y": -9.2532958984375, + "z": -0.053661346435546875 + }, + "queryAACube": { + "scale": 2.5651814937591553, + "x": 0.318722128868103, + "y": -1.0358866453170776, + "z": 2.663747787475586 + }, + "rotation": { + "w": 0.8220492601394653, + "x": -1.52587890625e-05, + "y": 0.5693598985671997, + "z": -0.0001068115234375 + }, + "script": "file:///C:/Users/wayne/development/tryAgainEntityScript.js", + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{81207b9c-e3f2-4ee1-bb81-0e14f37e4687}", + "lastEdited": 1535751362197613, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.8216662406921387, + "green": 0.15618896484375, + "red": 1.2409718036651611, + "x": -2.759028196334839, + "y": -9.34381103515625, + "z": -0.17833375930786133 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 1.0677666664123535, + "y": -0.017016112804412842, + "z": 3.648461103439331 + }, + "rotation": { + "w": 0.6747081279754639, + "x": -0.06532388925552368, + "y": -0.6342412233352661, + "z": 0.37175559997558594 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 0.25, + "clientOnly": false, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255, + "x": 255, + "y": 255, + "z": 255 + }, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 13.24000072479248, + "green": 13.24000072479248, + "red": 13.24000072479248, + "x": 13.24000072479248, + "y": 13.24000072479248, + "z": 13.24000072479248 + }, + "emitAcceleration": { + "blue": 0, + "green": 0.10000000149011612, + "red": 0, + "x": 0, + "y": 0.10000000149011612, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "emitRate": 6, + "emitSpeed": 0, + "id": "{b8f11bf5-410e-4e35-b4c3-91c86c01351d}", + "lastEdited": 1535751362196710, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "lifespan": 10, "maxParticles": 10, @@ -219,558 +863,6 @@ "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, - { - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 0 - }, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 11.117486953735352, - "green": 3.580313205718994, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 3.580313205718994, - "z": 11.117486953735352 - }, - "id": "{12a26c55-c411-4c4c-a124-cac9c80f9532}", - "lastEdited": 1535583681180522, - "lastEditedBy": "{f5457c14-32ca-45c2-9dde-4f5b30b3be1b}", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 1.9063844680786133, - "green": 1.15850830078125, - "red": 0.16632509231567383, - "x": -3.833674907684326, - "y": -8.34149169921875, - "z": -2.0936155319213867 - }, - "queryAACube": { - "scale": 11.681488037109375, - "x": -5.674418926239014, - "y": -4.6822357177734375, - "z": -3.934359550476074 - }, - "rotation": { - "w": 0.9666743278503418, - "x": -4.57763671875e-05, - "y": -0.2560006380081177, - "z": 1.52587890625e-05 - }, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "angularDamping": 0, - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{e8fd2c79-1303-49e4-8e4a-823272d6558c}", - "lastEdited": 1535583477862445, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 1.3116319179534912, - "green": 0.0772705078125, - "red": 2.705613613128662, - "x": -1.294386386871338, - "y": -9.4227294921875, - "z": -2.688368082046509 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 2.5324084758758545, - "y": -0.09593456983566284, - "z": 1.1384267807006836 - }, - "rotation": { - "w": -0.38777750730514526, - "x": -0.37337303161621094, - "y": -0.8409399390220642, - "z": 0.055222392082214355 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 9.030570983886719, - "green": 0.0719478651881218, - "red": 9.030570983886719, - "x": 9.030570983886719, - "y": 0.0719478651881218, - "z": 9.030570983886719 - }, - "id": "{85b4551d-c05d-4038-b188-30ad5db9c23a}", - "lastEdited": 1535583477861514, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/floor.fbx", - "name": "Floor", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 4.2324604988098145, - "green": 0.17547607421875, - "red": 4.802988529205322, - "x": 0.8029885292053223, - "y": -9.32452392578125, - "z": 0.23246049880981445 - }, - "queryAACube": { - "scale": 12.771358489990234, - "x": -1.582690715789795, - "y": -6.210203170776367, - "z": -2.1532187461853027 - }, - "rotation": { - "w": 0.8648051023483276, - "x": -1.52587890625e-05, - "y": 0.5020675659179688, - "z": -4.57763671875e-05 - }, - "shapeType": "simple-hull", - "type": "Model", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, - { - "clientOnly": false, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 0.06014331430196762, - "green": 2.582186460494995, - "red": 2.582186698913574, - "x": 2.582186698913574, - "y": 2.582186460494995, - "z": 0.06014331430196762 - }, - "id": "{8e4d8047-c2e2-4b2c-bfa2-c732d419d59a}", - "lastEdited": 1535583477860645, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/oopsDialog.fbx", - "name": "Oops Dialog", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 1.45927095413208, - "green": 1.6763916015625, - "red": 0, - "x": -4, - "y": -7.8236083984375, - "z": -2.54072904586792 - }, - "queryAACube": { - "scale": 3.6522583961486816, - "x": -1.8261291980743408, - "y": -0.14973759651184082, - "z": -0.36685824394226074 - }, - "rotation": { - "w": 0.8684672117233276, - "x": -4.57763671875e-05, - "y": 0.4957197904586792, - "z": -7.62939453125e-05 - }, - "type": "Model", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, - { - "angularDamping": 0, - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{4fc1436d-00e7-43ec-83e1-f70cff544885}", - "lastEdited": 1535583477862827, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 3.8216662406921387, - "green": 0.15618896484375, - "red": 1.2409718036651611, - "x": -2.759028196334839, - "y": -9.34381103515625, - "z": -0.17833375930786133 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 1.0677666664123535, - "y": -0.017016112804412842, - "z": 3.648461103439331 - }, - "rotation": { - "w": 0.6747081279754639, - "x": -0.06532388925552368, - "y": -0.6342412233352661, - "z": 0.37175559997558594 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 0 - }, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 11.117486953735352, - "green": 3.580313205718994, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 3.580313205718994, - "z": 11.117486953735352 - }, - "id": "{81a7c620-88ea-4056-b148-449d963c7eb5}", - "lastEdited": 1535583477860260, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 0, - "green": 1.1583251953125, - "red": 4.971565246582031, - "x": 0.9715652465820312, - "y": -8.3416748046875, - "z": -4 - }, - "queryAACube": { - "scale": 11.681488037109375, - "x": -0.8691787719726562, - "y": -4.6824188232421875, - "z": -5.8407440185546875 - }, - "rotation": { - "w": 0.8637980222702026, - "x": -4.57763671875e-05, - "y": 0.5038070678710938, - "z": -1.52587890625e-05 - }, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 2.1097896099090576, - "green": 0.04847164824604988, - "red": 1.458284616470337, - "x": 1.458284616470337, - "y": 0.04847164824604988, - "z": 2.1097896099090576 - }, - "id": "{09436993-6042-49ff-bfec-0fd28f792251}", - "lastEdited": 1535583477863518, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal1.fbx", - "name": "Try Again", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 3.946338653564453, - "green": 0.2467041015625, - "red": 1.6013128757476807, - "x": -2.3986871242523193, - "y": -9.2532958984375, - "z": -0.053661346435546875 - }, - "queryAACube": { - "scale": 2.5651814937591553, - "x": 0.318722128868103, - "y": -1.0358866453170776, - "z": 2.663747787475586 - }, - "rotation": { - "w": 0.8220492601394653, - "x": -1.52587890625e-05, - "y": 0.5693598985671997, - "z": -0.0001068115234375 - }, - "script": "file:///C:/Users/wayne/development/tryAgainEntityScript.js", - "shapeType": "static-mesh", - "type": "Model", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, - { - "angularDamping": 0, - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{86188ae0-6b36-4753-a5c0-0ba31b8ca89a}", - "lastEdited": 1535583477861108, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 1.3116319179534912, - "green": 0, - "red": 2.705613613128662, - "x": -1.294386386871338, - "y": -9.5, - "z": -2.688368082046509 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 2.5324084758758545, - "y": -0.17320507764816284, - "z": 1.1384267807006836 - }, - "rotation": { - "w": 0.9127794504165649, - "x": 0.2575265169143677, - "y": 0.15553522109985352, - "z": 0.2761729955673218 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "angularDamping": 0, - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{ffa954a9-43e4-47b1-82c9-b921ed3414b0}", - "lastEdited": 1535583477861316, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 3.8216662406921387, - "green": 0.0772705078125, - "red": 1.2409718036651611, - "x": -2.759028196334839, - "y": -9.4227294921875, - "z": -0.17833375930786133 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 1.0677666664123535, - "y": -0.09593456983566284, - "z": 3.648461103439331 - }, - "rotation": { - "w": 0.926024317741394, - "x": 0.20308232307434082, - "y": -0.010269343852996826, - "z": 0.3179827928543091 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "angularDamping": 0, - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{e86df231-a55a-4ca7-ae68-d3271a543d10}", - "lastEdited": 1535583477860886, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 3.8216662406921387, - "green": 0, - "red": 1.2409718036651611, - "x": -2.759028196334839, - "y": -9.5, - "z": -0.17833375930786133 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 1.0677666664123535, - "y": -0.17320507764816284, - "z": 3.648461103439331 - }, - "rotation": { - "w": 0.6444342136383057, - "x": -0.08220034837722778, - "y": -0.6649118661880493, - "z": 0.3684900999069214 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "angularDamping": 0, - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{252ffb06-db92-4732-9315-7ee293c24ab8}", - "lastEdited": 1535583477862267, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 1.3116319179534912, - "green": 0.15618896484375, - "red": 2.705613613128662, - "x": -1.294386386871338, - "y": -9.34381103515625, - "z": -2.688368082046509 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 2.5324084758758545, - "y": -0.017016112804412842, - "z": 1.1384267807006836 - }, - "rotation": { - "w": 0.46953535079956055, - "x": -0.16719311475753784, - "y": -0.7982757091522217, - "z": 0.3380941152572632 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 0 - }, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 11.117486953735352, - "green": 3.580313205718994, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 3.580313205718994, - "z": 11.117486953735352 - }, - "id": "{1c93807b-311d-48cd-af5e-b477d33b2811}", - "lastEdited": 1535583477860455, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 5.268576622009277, - "green": 1.1588134765625, - "red": 6.100250244140625, - "x": 2.100250244140625, - "y": -8.3411865234375, - "z": 1.2685766220092773 - }, - "queryAACube": { - "scale": 11.681488037109375, - "x": 0.2595062255859375, - "y": -4.6819305419921875, - "z": -0.5721673965454102 - }, - "rotation": { - "w": 0.9662165641784668, - "x": -4.57763671875e-05, - "y": -0.2576791048049927, - "z": 1.52587890625e-05 - }, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, { "alpha": 0, "alphaFinish": 0, @@ -797,7 +889,7 @@ "y": 204, "z": 255 }, - "created": "2018-08-29T22:57:33Z", + "created": "2018-08-31T21:35:56Z", "dimensions": { "blue": 2.5, "green": 2.5, @@ -831,8 +923,8 @@ "emitRate": 2, "emitSpeed": 0, "emitterShouldTrail": true, - "id": "{4cb534d1-5494-4aaa-aa28-ff5cee6e0b8f}", - "lastEdited": 1535583477862081, + "id": "{4e72df75-67d7-4ce2-bdea-e78adae8904a}", + "lastEdited": 1535751362199482, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "lifespan": 10, "maxParticles": 40, @@ -870,144 +962,6 @@ "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, - { - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 0 - }, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 11.117486953735352, - "green": 3.580313205718994, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 3.580313205718994, - "z": 11.117486953735352 - }, - "id": "{a58a8ffe-17ee-4154-b8bb-54e2f0ca9a9d}", - "lastEdited": 1535583477862953, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 6.1806135177612305, - "green": 1.1588134765625, - "red": 1.4755167961120605, - "x": -2.5244832038879395, - "y": -8.3411865234375, - "z": 2.1806135177612305 - }, - "queryAACube": { - "scale": 11.681488037109375, - "x": -4.365227222442627, - "y": -4.6819305419921875, - "z": 0.33986949920654297 - }, - "rotation": { - "w": 0.8637980222702026, - "x": -4.57763671875e-05, - "y": 0.5038070678710938, - "z": -1.52587890625e-05 - }, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "alpha": 0, - "alphaFinish": 0, - "alphaStart": 0.25, - "clientOnly": false, - "colorFinish": { - "blue": 0, - "green": 0, - "red": 0, - "x": 0, - "y": 0, - "z": 0 - }, - "colorStart": { - "blue": 255, - "green": 255, - "red": 255, - "x": 255, - "y": 255, - "z": 255 - }, - "created": "2018-08-29T22:57:33Z", - "dimensions": { - "blue": 13.24000072479248, - "green": 13.24000072479248, - "red": 13.24000072479248, - "x": 13.24000072479248, - "y": 13.24000072479248, - "z": 13.24000072479248 - }, - "emitAcceleration": { - "blue": 0, - "green": 0.10000000149011612, - "red": 0, - "x": 0, - "y": 0.10000000149011612, - "z": 0 - }, - "emitDimensions": { - "blue": 1, - "green": 1, - "red": 1, - "x": 1, - "y": 1, - "z": 1 - }, - "emitOrientation": { - "w": 1, - "x": -1.52587890625e-05, - "y": -1.52587890625e-05, - "z": -1.52587890625e-05 - }, - "emitRate": 6, - "emitSpeed": 0, - "emitterShouldTrail": true, - "id": "{b6c56e83-f098-422d-a0e8-fd1497c62fbe}", - "lastEdited": 1535583477863251, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "lifespan": 10, - "maxParticles": 10, - "name": "Stars", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "particleRadius": 0.07000000029802322, - "polarFinish": 3.1415927410125732, - "position": { - "blue": 3.78922963142395, - "green": 0.5220947265625, - "red": 1.1928560733795166, - "x": -2.8071439266204834, - "y": -8.9779052734375, - "z": -0.2107703685760498 - }, - "queryAACube": { - "scale": 22.932353973388672, - "x": -10.273321151733398, - "y": -10.944082260131836, - "z": -7.676947593688965 - }, - "radiusFinish": 0, - "radiusStart": 0, - "rotation": { - "w": 0.996429443359375, - "x": -1.52587890625e-05, - "y": -0.08442819118499756, - "z": -4.57763671875e-05 - }, - "speedSpread": 0, - "spinFinish": null, - "spinStart": null, - "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", - "type": "ParticleEffect", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, { "alpha": 0, "alphaFinish": 0, @@ -1034,7 +988,7 @@ "y": 227, "z": 211 }, - "created": "2018-08-29T22:57:33Z", + "created": "2018-08-31T21:35:56Z", "dimensions": { "blue": 2.5, "green": 2.5, @@ -1067,8 +1021,8 @@ }, "emitRate": 2, "emitSpeed": 0, - "id": "{f87fab56-c8d8-4ff8-abf0-35a76dfccf2b}", - "lastEdited": 1535583477862685, + "id": "{3482eecd-0acc-4fb9-bd67-b160fa7c14d7}", + "lastEdited": 1535751362200185, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", "lifespan": 10, "maxParticles": 40, @@ -1108,51 +1062,94 @@ }, { "clientOnly": false, - "created": "2018-08-29T22:57:33Z", + "created": "2018-08-31T21:35:56Z", "dimensions": { - "blue": 2.1097896099090576, - "green": 0.04847164824604988, - "red": 1.458284616470337, - "x": 1.458284616470337, - "y": 0.04847164824604988, - "z": 2.1097896099090576 + "blue": 0.06014331430196762, + "green": 2.582186460494995, + "red": 2.582186698913574, + "x": 2.582186698913574, + "y": 2.582186460494995, + "z": 0.06014331430196762 }, - "id": "{1af53cfc-0cce-467a-96e0-a937f2651ce2}", - "lastEdited": 1535583477863389, + "id": "{729e9f39-5750-42a4-97a8-0fc79b300a32}", + "lastEdited": 1535751362197365, "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal2.fbx", - "name": "Back", + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/oopsDialog.fbx", + "name": "Oops Dialog", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { - "blue": 1.5835940837860107, - "green": 0.2467041015625, - "red": 3.0345542430877686, - "x": -0.9654457569122314, - "y": -9.2532958984375, - "z": -2.4164059162139893 + "blue": 1.45927095413208, + "green": 1.6763916015625, + "red": 0, + "x": -4, + "y": -7.8236083984375, + "z": -2.54072904586792 }, "queryAACube": { - "scale": 2.5651814937591553, - "x": 1.751963496208191, - "y": -1.0358866453170776, - "z": 0.3010033369064331 + "scale": 3.6522583961486816, + "x": -1.8261291980743408, + "y": -0.14973759651184082, + "z": -0.36685824394226074 }, "rotation": { - "w": 0.9084458351135254, - "x": -1.52587890625e-05, - "y": 0.4179598093032837, - "z": -0.0001068115234375 + "w": 0.8684672117233276, + "x": -4.57763671875e-05, + "y": 0.4957197904586792, + "z": -7.62939453125e-05 }, - "script": "file:///C:/Users/wayne/development/tryAgainEntityScript.js", - "shapeType": "static-mesh", "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-08-31T21:35:56Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{bd032130-bbd7-4c0b-9b98-ab9c9362be9b}", + "lastEdited": 1535751362198499, + "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.8216662406921387, + "green": 0.0772705078125, + "red": 1.2409718036651611, + "x": -2.759028196334839, + "y": -9.4227294921875, + "z": -0.17833375930786133 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 1.0677666664123535, + "y": -0.09593456983566284, + "z": 3.648461103439331 + }, + "rotation": { + "w": 0.926024317741394, + "x": 0.20308232307434082, + "y": -0.010269343852996826, + "z": 0.3179827928543091 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false } ], - "Paths": - { - "/": "/1.46,-9,0.7/0,0.487,0,0.86663" - }, - "Id": "{351e561a-ee5e-4e8b-87ab-d28677d3b374}", + "Id": "{99bc5288-05fd-4d80-a406-a44c948f7066}", "Version": 93 -} +} \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 86a1421127..e50473bf29 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1185,6 +1185,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(domainURLChanged(QUrl)), SLOT(domainURLChanged(QUrl))); + connect(&domainHandler, SIGNAL(redirectToErrorDomainURL(QUrl)), SLOT(goToErrorDomainURL(QUrl))); connect(&domainHandler, &DomainHandler::domainURLChanged, [](QUrl domainURL){ setCrashAnnotation("domain", domainURL.toString().toStdString()); }); @@ -2251,6 +2252,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(this, &QCoreApplication::aboutToQuit, this, &Application::addAssetToWorldMessageClose); connect(&domainHandler, &DomainHandler::domainURLChanged, this, &Application::addAssetToWorldMessageClose); + connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &Application::addAssetToWorldMessageClose); updateSystemTabletMode(); @@ -2343,14 +2345,6 @@ void Application::domainConnectionRefused(const QString& reasonMessage, int reas } } -void Application::domainConnectionRedirect() { - auto addressManager = DependencyManager::get(); - - addressManager->handleLookupString(REDIRECT_HIFI_ADDRESS); - getMyAvatar()->setWorldVelocity(glm::vec3(0.0f)); -} - - QString Application::getUserAgent() { if (QThread::currentThread() != thread()) { QString userAgent; @@ -3481,7 +3475,7 @@ void Application::setIsServerlessMode(bool serverlessDomain) { } } -void Application::loadServerlessDomain(QUrl domainURL) { +void Application::loadServerlessDomain(QUrl domainURL, bool errorDomain) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL)); return; @@ -3515,8 +3509,11 @@ void Application::loadServerlessDomain(QUrl domainURL) { } std::map namedPaths = tmpTree->getNamedPaths(); - nodeList->getDomainHandler().connectedToServerless(namedPaths); - + if (errorDomain) { + nodeList->getDomainHandler().loadedErrorDomain(namedPaths); + } else { + nodeList->getDomainHandler().connectedToServerless(namedPaths); + } _fullSceneReceivedCounter++; } @@ -6381,6 +6378,7 @@ void Application::clearDomainAvatars() { void Application::domainURLChanged(QUrl domainURL) { // disable physics until we have enough information about our new location to not cause craziness. resetPhysicsReadyInformation(); + auto urlStr = domainURL.toString().toStdString(); setIsServerlessMode(domainURL.scheme() != URL_SCHEME_HIFI); if (isServerlessMode()) { loadServerlessDomain(domainURL); @@ -6388,6 +6386,17 @@ void Application::domainURLChanged(QUrl domainURL) { updateWindowTitle(); } +void Application::goToErrorDomainURL(QUrl errorDomainURL) { + // disable physics until we have enough information about our new location to not cause craziness. + resetPhysicsReadyInformation(); + auto urlStr = errorDomainURL.toString().toStdString(); + setIsServerlessMode(errorDomainURL.scheme() != URL_SCHEME_HIFI); + if (isServerlessMode()) { + loadServerlessDomain(errorDomainURL, true); + } + updateWindowTitle(); +} + void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; diff --git a/interface/src/Application.h b/interface/src/Application.h index e38b5a3919..7fe88e9b6a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -426,7 +426,7 @@ public slots: void setPreferredCursor(const QString& cursor); void setIsServerlessMode(bool serverlessDomain); - void loadServerlessDomain(QUrl domainURL); + void loadServerlessDomain(QUrl domainURL, bool errorDomain = false); void updateVerboseLogging(); @@ -465,6 +465,7 @@ private slots: void setSessionUUID(const QUuid& sessionUUID) const; void domainURLChanged(QUrl domainURL); + void goToErrorDomainURL(QUrl errorDomainURL); void updateWindowTitle() const; void nodeAdded(SharedNodePointer node) const; void nodeActivated(SharedNodePointer node); @@ -474,7 +475,6 @@ private slots: void updateDisplayMode(); void setDisplayPlugin(DisplayPluginPointer newPlugin); void domainConnectionRefused(const QString& reasonMessage, int reason, const QString& extraInfo); - void domainConnectionRedirect(); void addAssetToWorldCheckModelSize(); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 01db8dfd79..62cd79a609 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -114,6 +114,9 @@ QUrl AddressManager::currentFacingPublicAddress() const { return shareableAddress; } +QUrl AddressManager::lastAddress() const { + return _lastVisitedURL; +} void AddressManager::loadSettings(const QString& lookupString) { #if defined(USE_GLES) && defined(Q_OS_WIN) @@ -151,6 +154,12 @@ void AddressManager::goForward() { } } +void AddressManager::goToLastAddress() { + // this should always return something as long as the URL isn't empty. + auto urlStr = _lastVisitedURL.toString().toStdString(); + handleUrl(_lastVisitedURL, LookupTrigger::AttemptedRefresh); +} + void AddressManager::storeCurrentAddress() { auto url = currentAddress(); @@ -250,9 +259,12 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_USER, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // in case we're failing to connect to where we thought this user was // store their username as previous lookup so we can refresh their location via API - _previousLookup = lookupUrl; + _previousAPILookup = lookupUrl; } else { // we're assuming this is either a network address or global place name // check if it is a network address first @@ -262,8 +274,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_NETWORK_ADDRESS, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // a network address lookup clears the previous lookup since we don't expect to re-attempt it - _previousLookup.clear(); + _previousAPILookup.clear(); // If the host changed then we have already saved to history if (hostChanged) { @@ -281,8 +296,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { } else if (handleDomainID(lookupUrl.host())){ UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_DOMAIN_ID, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info - _previousLookup = lookupUrl; + _previousAPILookup = lookupUrl; // no place name - this is probably a domain ID // try to look up the domain ID on the metaverse API @@ -290,8 +308,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { } else { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_PLACE, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // store this place name as the previous lookup in case we fail to connect and want to refresh API info - _previousLookup = lookupUrl; + _previousAPILookup = lookupUrl; // wasn't an address - lookup the place name // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after @@ -305,7 +326,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { qCDebug(networking) << "Going to relative path" << lookupUrl.path(); // a path lookup clears the previous lookup since we don't expect to re-attempt it - _previousLookup.clear(); + _previousAPILookup.clear(); // if this is a relative path then handle it as a relative viewpoint handlePath(lookupUrl.path(), trigger, true); @@ -317,7 +338,10 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // be loaded over http(s) // lookupUrl.scheme() == URL_SCHEME_HTTP || // lookupUrl.scheme() == URL_SCHEME_HTTPS || - _previousLookup.clear(); + // TODO once a file can return a connection refusal if there were to be some kind of load error, we'd + // need to store the previous domain tried in _lastVisitedURL. For now , do not store it. + + _previousAPILookup.clear(); _shareablePlaceName.clear(); if (lookupUrl.toString() != REDIRECT_HIFI_ADDRESS) { setDomainInfo(lookupUrl, trigger); @@ -386,7 +410,7 @@ void AddressManager::handleAPIResponse(QNetworkReply* requestReply) { QJsonObject dataObject = responseObject["data"].toObject(); // Lookup succeeded, don't keep re-trying it (especially on server restarts) - _previousLookup.clear(); + _previousAPILookup.clear(); if (!dataObject.isEmpty()) { goToAddressFromObject(dataObject.toVariantMap(), requestReply); @@ -552,7 +576,7 @@ void AddressManager::handleAPIError(QNetworkReply* errorReply) { if (errorReply->error() == QNetworkReply::ContentNotFoundError) { // if this is a lookup that has no result, don't keep re-trying it - _previousLookup.clear(); + _previousAPILookup.clear(); emit lookupResultIsNotFound(); } @@ -847,8 +871,8 @@ void AddressManager::goToUser(const QString& username, bool shouldMatchOrientati void AddressManager::refreshPreviousLookup() { // if we have a non-empty previous lookup, fire it again now (but don't re-store it in the history) - if (!_previousLookup.isEmpty()) { - handleUrl(_previousLookup, LookupTrigger::AttemptedRefresh); + if (!_previousAPILookup.isEmpty()) { + handleUrl(_previousAPILookup, LookupTrigger::AttemptedRefresh); } else { handleUrl(currentAddress(), LookupTrigger::AttemptedRefresh); } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 245517d8cd..fab2bdd6cb 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -145,6 +145,7 @@ public: UserInput, Back, Forward, + //Retry, StartupFromSettings, DomainPathResponse, Internal, @@ -165,6 +166,8 @@ public: QString currentPath(bool withOrientation = true) const; QString currentFacingPath() const; + QUrl lastAddress() const; + const QUuid& getRootPlaceID() const { return _rootPlaceID; } QString getPlaceName() const; QString getDomainID() const; @@ -246,6 +249,12 @@ public slots: */ void goToUser(const QString& username, bool shouldMatchOrientation = true); + /**jsdoc + * Go to the last address tried. This will be the last URL tried from location.handleLookupString + * @function location.goToLastAddress + */ + void goToLastAddress(); + /**jsdoc * Refresh the current address, e.g., after connecting to a domain in order to position the user to the desired location. * @function location.refreshPreviousLookup @@ -447,6 +456,7 @@ private: void addCurrentAddressToHistory(LookupTrigger trigger); QUrl _domainURL; + QUrl _lastVisitedURL; QUuid _rootPlaceID; PositionGetter _positionGetter; @@ -460,7 +470,7 @@ private: QString _newHostLookupPath; - QUrl _previousLookup; + QUrl _previousAPILookup; }; #endif // hifi_AddressManager_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 827232129f..0febe1e155 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -99,6 +99,7 @@ void DomainHandler::softReset() { clearSettings(); + _isInErrorState = false; _connectionDenialsSinceKeypairRegen = 0; _checkInPacketsSinceLastReply = 0; @@ -129,6 +130,7 @@ void DomainHandler::hardReset() { } void DomainHandler::setErrorDomainURL(const QUrl& url) { + _errorDomainURL = url; return; } @@ -175,7 +177,8 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { domainPort = DEFAULT_DOMAIN_SERVER_PORT; } - if (_domainURL != domainURL || _sockAddr.getPort() != domainPort) { + // if it's in the error state, reset and try again. + if ((_domainURL != domainURL || _sockAddr.getPort() != domainPort) || _isInErrorState) { // re-set the domain info so that auth information is reloaded hardReset(); @@ -210,7 +213,8 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) { - if (_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) { + // if it's in the error state, reset and try again. + if ((_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) || _isInErrorState) { // re-set the domain info to connect to new domain hardReset(); @@ -320,6 +324,17 @@ void DomainHandler::connectedToServerless(std::map namedPaths) setIsConnected(true); } +void DomainHandler::loadedErrorDomain(std::map namedPaths) { + auto lookup = namedPaths.find("/"); + QString viewpoint; + if (lookup != namedPaths.end()) { + viewpoint = lookup->second; + } else { + viewpoint = DOMAIN_SPAWNING_POINT; + } + DependencyManager::get()->goToViewpointForPath(viewpoint, QString()); +} + void DomainHandler::requestDomainSettings() { qCDebug(networking) << "Requesting settings from domain server"; @@ -461,7 +476,7 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer namedPaths); + void DomainHandler::loadedErrorDomain(std::map namedPaths); + QString getViewPointFromNamedPath(QString namedPath); bool hasSettings() const { return !_settingsObject.isEmpty(); } @@ -182,7 +184,7 @@ signals: void settingsReceiveFail(); void domainConnectionRefused(QString reasonMessage, int reason, const QString& extraInfo); - void redirectToErrorDomainURL(); + void redirectToErrorDomainURL(QUrl errorDomaunURL); void limitOfSilentDomainCheckInsReached(); From 96aa0549be8d21d05ea5b3826b5d9a869d50c225 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sat, 1 Sep 2018 12:08:49 -0700 Subject: [PATCH 203/744] AVX2 implementation of crossfade_4x2() --- libraries/audio/src/AudioHRTF.cpp | 6 ++- libraries/audio/src/avx2/AudioHRTF_avx2.cpp | 57 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index 655527a65c..eea01b703b 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -469,8 +469,12 @@ static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3] (*f)(src, dst, coef, state, numFrames); // dispatch } +void crossfade_4x2_AVX2(float* src, float* dst, const float* win, int numFrames); + static void crossfade_4x2(float* src, float* dst, const float* win, int numFrames) { - crossfade_4x2_SSE(src, dst, win, numFrames); + + static auto f = cpuSupportsAVX2() ? crossfade_4x2_AVX2 : crossfade_4x2_SSE; + (*f)(src, dst, win, numFrames); // dispatch } static void interpolate(const float* src0, const float* src1, float* dst, float frac, float gain) { diff --git a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp index 9000df367f..b82098b72f 100644 --- a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp +++ b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp @@ -169,4 +169,61 @@ void biquad2_4x4_AVX2(float* src, float* dst, float coef[5][8], float state[3][8 _mm256_zeroupper(); } +// crossfade 4 inputs into 2 outputs with accumulation (interleaved) +void crossfade_4x2_AVX2(float* src, float* dst, const float* win, int numFrames) { + + assert(numFrames % 8 == 0); + + for (int i = 0; i < numFrames; i += 8) { + + __m256 f0 = _mm256_loadu_ps(&win[i]); + + __m256 x0 = _mm256_castps128_ps256(_mm_loadu_ps(&src[4*i+0])); + __m256 x1 = _mm256_castps128_ps256(_mm_loadu_ps(&src[4*i+4])); + __m256 x2 = _mm256_castps128_ps256(_mm_loadu_ps(&src[4*i+8])); + __m256 x3 = _mm256_castps128_ps256(_mm_loadu_ps(&src[4*i+12])); + + x0 = _mm256_insertf128_ps(x0, _mm_loadu_ps(&src[4*i+16]), 1); + x1 = _mm256_insertf128_ps(x1, _mm_loadu_ps(&src[4*i+20]), 1); + x2 = _mm256_insertf128_ps(x2, _mm_loadu_ps(&src[4*i+24]), 1); + x3 = _mm256_insertf128_ps(x3, _mm_loadu_ps(&src[4*i+28]), 1); + + __m256 y0 = _mm256_loadu_ps(&dst[2*i+0]); + __m256 y1 = _mm256_loadu_ps(&dst[2*i+8]); + + // deinterleave (4x4 matrix transpose) + __m256 t0 = _mm256_unpacklo_ps(x0, x1); + __m256 t1 = _mm256_unpackhi_ps(x0, x1); + __m256 t2 = _mm256_unpacklo_ps(x2, x3); + __m256 t3 = _mm256_unpackhi_ps(x2, x3); + + x0 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(1,0,1,0)); + x1 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(3,2,3,2)); + x2 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(1,0,1,0)); + x3 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(3,2,3,2)); + + // crossfade + x0 = _mm256_sub_ps(x0, x2); + x1 = _mm256_sub_ps(x1, x3); + x2 = _mm256_fmadd_ps(f0, x0, x2); + x3 = _mm256_fmadd_ps(f0, x1, x3); + + // interleave + t0 = _mm256_unpacklo_ps(x2, x3); + t1 = _mm256_unpackhi_ps(x2, x3); + + x0 = _mm256_permute2f128_ps(t0, t1, 0x20); + x1 = _mm256_permute2f128_ps(t0, t1, 0x31); + + // accumulate + y0 = _mm256_add_ps(y0, x0); + y1 = _mm256_add_ps(y1, x1); + + _mm256_storeu_ps(&dst[2*i+0], y0); + _mm256_storeu_ps(&dst[2*i+8], y1); + } + + _mm256_zeroupper(); +} + #endif From e8d922a56cb82adf7d9e0b17c2fe80dcfa4c952d Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Mon, 3 Sep 2018 02:48:07 -0700 Subject: [PATCH 204/744] Interleaving the attributes and relying on just 3 input buffers --- libraries/fbx/src/FBXReader_Mesh.cpp | 105 ++++++++++++++++-- .../gpu-gl/src/gpu/gl45/GL45BackendInput.cpp | 11 +- libraries/gpu/src/gpu/Stream.h | 2 + libraries/graphics/src/graphics/Geometry.cpp | 7 ++ libraries/graphics/src/graphics/Geometry.h | 3 + 5 files changed, 116 insertions(+), 12 deletions(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index 801edddb06..13a2c697a0 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -585,13 +585,15 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { FBXMesh& fbxMesh = extractedMesh; graphics::MeshPointer mesh(new graphics::Mesh()); + bool blendShapes = !fbxMesh.blendshapes.empty(); + int numVerts = extractedMesh.vertices.size(); // Grab the vertices in a buffer auto vb = std::make_shared(); vb->setData(extractedMesh.vertices.size() * sizeof(glm::vec3), (const gpu::Byte*) extractedMesh.vertices.data()); gpu::BufferView vbv(vb, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - mesh->setVertexBuffer(vbv); + // mesh->setVertexBuffer(vbv); if (!fbxMesh.normals.empty() && fbxMesh.tangents.empty()) { // Fill with a dummy value to force tangents to be present if there are normals @@ -634,7 +636,10 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // Normals and tangents are interleaved const int normalsOffset = 0; const int tangentsOffset = normalsOffset + sizeof(NormalType); - const int colorsOffset = normalsOffset + normalsSize + tangentsSize; + const int totalNTSize = normalsOffset + normalsSize + tangentsSize; + //const int colorsOffset = normalsOffset + normalsSize + tangentsSize; + + const int colorsOffset = 0; const int texCoordsOffset = colorsOffset + colorsSize; const int texCoords1Offset = texCoordsOffset + texCoordsSize; const int clusterIndicesOffset = texCoords1Offset + texCoords1Size; @@ -642,6 +647,10 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { const int totalAttributeSize = clusterWeightsOffset + clusterWeightsSize; // Copy all attribute data in a single attribute buffer + + auto attribNTBuffer = std::make_shared(); + attribNTBuffer->resize(totalNTSize); + auto attribBuffer = std::make_shared(); attribBuffer->resize(totalAttributeSize); @@ -665,9 +674,10 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { normalsAndTangents.push_back(packedNormal); normalsAndTangents.push_back(packedTangent); } - attribBuffer->setSubData(normalsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.data()); + attribNTBuffer->setSubData(normalsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.data()); } + if (colorsSize > 0) { #if FBX_PACK_COLORS std::vector colors; @@ -723,50 +733,123 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { } attribBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) fbxMesh.clusterWeights.constData()); + auto vf = std::make_shared(); + auto vbs = std::make_shared(); + + gpu::Offset buf0Offset = 12; + vf->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + vbs->addBuffer(vb, 0, buf0Offset); + + gpu::Offset buf1Offset = 0; if (normalsSize) { - mesh->addAttribute(gpu::Stream::NORMAL, + /* mesh->addAttribute(gpu::Stream::NORMAL, graphics::BufferView(attribBuffer, normalsOffset, normalsAndTangentsSize, normalsAndTangentsStride, FBX_NORMAL_ELEMENT)); mesh->addAttribute(gpu::Stream::TANGENT, graphics::BufferView(attribBuffer, tangentsOffset, normalsAndTangentsSize, normalsAndTangentsStride, FBX_NORMAL_ELEMENT)); +*/ + vf->setAttribute(gpu::Stream::NORMAL, 1, FBX_NORMAL_ELEMENT, 0); + vf->setAttribute(gpu::Stream::TANGENT, 1, FBX_NORMAL_ELEMENT, 4); + buf1Offset = 8; + vbs->addBuffer(attribNTBuffer, 0, buf1Offset); } + + gpu::Offset buf2Offset = 0; if (colorsSize) { - mesh->addAttribute(gpu::Stream::COLOR, + /* mesh->addAttribute(gpu::Stream::COLOR, graphics::BufferView(attribBuffer, colorsOffset, colorsSize, FBX_COLOR_ELEMENT)); +*/ + vf->setAttribute(gpu::Stream::COLOR, 2, FBX_COLOR_ELEMENT, buf2Offset); + buf2Offset += 4; } if (texCoordsSize) { - mesh->addAttribute(gpu::Stream::TEXCOORD, + /* mesh->addAttribute(gpu::Stream::TEXCOORD, graphics::BufferView( attribBuffer, texCoordsOffset, texCoordsSize, gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); + */ vf->setAttribute(gpu::Stream::TEXCOORD, 2, gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV), buf2Offset); + buf2Offset += 4; } if (texCoords1Size) { - mesh->addAttribute( gpu::Stream::TEXCOORD1, + /* mesh->addAttribute( gpu::Stream::TEXCOORD1, graphics::BufferView(attribBuffer, texCoords1Offset, texCoords1Size, gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); + */ vf->setAttribute(gpu::Stream::TEXCOORD1, 2, gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV), buf2Offset); + buf2Offset += 4; } else if (texCoordsSize) { - mesh->addAttribute(gpu::Stream::TEXCOORD1, + /* mesh->addAttribute(gpu::Stream::TEXCOORD1, graphics::BufferView(attribBuffer, texCoordsOffset, texCoordsSize, gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); + */ vf->setAttribute(gpu::Stream::TEXCOORD1, 2, gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV), buf2Offset - 4); } if (clusterIndicesSize) { if (fbxMesh.clusters.size() < UINT8_MAX) { - mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, + /* mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, graphics::BufferView(attribBuffer, clusterIndicesOffset, clusterIndicesSize, gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW))); +*/ + vf->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, 2, gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW), buf2Offset); + buf2Offset += 4; + } else { - mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, + /* mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, graphics::BufferView(attribBuffer, clusterIndicesOffset, clusterIndicesSize, gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW))); + */ vf->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, 2, gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW), buf2Offset); + buf2Offset += 8; } } if (clusterWeightsSize) { - mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, + /* mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, graphics::BufferView(attribBuffer, clusterWeightsOffset, clusterWeightsSize, gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW))); + */ vf->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, 2, gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW), buf2Offset); + buf2Offset += 8; } + { + auto vColorOffset = 0; + auto vColorSize = colorsSize / numVerts; + + auto vTexcoord0Offset = vColorOffset + vColorSize; + auto vTexcoord0Size = texCoordsSize / numVerts; + + auto vTexcoord1Offset = vTexcoord0Offset + vTexcoord0Size; + auto vTexcoord1Size = texCoords1Size / numVerts; + + auto vClusterIndiceOffset = vTexcoord1Offset + vTexcoord1Size; + auto vClusterIndiceSize = clusterIndicesSize / numVerts; + + auto vClusterWeightOffset = vClusterIndiceOffset + vClusterIndiceSize; + auto vClusterWeightSize = clusterWeightsSize / numVerts; + + auto vStride = vClusterWeightOffset + vClusterWeightSize; + //int vStride = buf2Offset; + std::vector dest; + dest.resize(totalAttributeSize); + auto vDest = dest.data(); + + auto source = attribBuffer->getData(); + + + for (int i = 0; i < numVerts; i++) { + + if (vColorSize) memcpy(vDest + vColorOffset, source + colorsOffset + i * vColorSize, vColorSize); + if (vTexcoord0Size) memcpy(vDest + vTexcoord0Offset, source + texCoordsOffset + i * vTexcoord0Size, vTexcoord0Size); + if (vTexcoord1Size) memcpy(vDest + vTexcoord1Offset, source + texCoords1Offset + i * vTexcoord1Size, vTexcoord1Size); + if (vClusterIndiceSize) memcpy(vDest + vClusterIndiceOffset, source + clusterIndicesOffset + i * vClusterIndiceSize, vClusterIndiceSize); + if (vClusterWeightSize) memcpy(vDest + vClusterWeightOffset, source + clusterWeightsOffset + i * vClusterWeightSize, vClusterWeightSize); + + vDest += vStride; + } + + attribBuffer->setData(totalAttributeSize, dest.data()); + + vbs->addBuffer(attribBuffer, 0, vStride); + } + + mesh->setVertexFormatAndStream(vf, vbs); unsigned int totalIndices = 0; foreach(const FBXMeshPart& part, extractedMesh.parts) { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp index cc3e609bda..c914b9f84d 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp @@ -27,6 +27,8 @@ void GL45Backend::resetInputStage() { } void GL45Backend::updateInput() { + // PROFILE_RANGE(render_gpu, __FUNCTION__); + bool isStereoNow = isStereo(); // track stereo state change potentially happening wihtout changing the input format // this is a rare case requesting to invalid the format @@ -36,6 +38,7 @@ void GL45Backend::updateInput() { _input._lastUpdateStereoState = isStereoNow; if (_input._invalidFormat) { + PROFILE_RANGE(render_gpu, "bindInputFormat"); InputStageState::ActivationCache newActivation; // Assign the vertex format required @@ -128,16 +131,22 @@ void GL45Backend::updateInput() { } if (_input._invalidBuffers.any()) { + // PROFILE_RANGE(render_gpu, "bindInputBuffers"); auto vbo = _input._bufferVBOs.data(); auto offset = _input._bufferOffsets.data(); auto stride = _input._bufferStrides.data(); - for (GLuint buffer = 0; buffer < _input._buffers.size(); buffer++, vbo++, offset++, stride++) { + int numSet = 0; + auto numBuffers = _input._buffers.size(); + for (GLuint buffer = 0; buffer < numBuffers; buffer++, vbo++, offset++, stride++) { if (_input._invalidBuffers.test(buffer)) { glBindVertexBuffer(buffer, (*vbo), (*offset), (GLsizei)(*stride)); + numSet++; } } + PROFILE_COUNTER_IF_CHANGED(render_gpu, "numVBSbound", int, numSet); + _input._invalidBuffers.reset(); (void)CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h index 0def1ab201..2e1ed1d83c 100644 --- a/libraries/gpu/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -152,6 +152,8 @@ public: BufferStream makeRangedStream(uint32 offset, uint32 count = -1) const; + BufferStream& operator = (const BufferStream& src) = default; + protected: Buffers _buffers; Offsets _offsets; diff --git a/libraries/graphics/src/graphics/Geometry.cpp b/libraries/graphics/src/graphics/Geometry.cpp index d43c773249..3f5f484b08 100755 --- a/libraries/graphics/src/graphics/Geometry.cpp +++ b/libraries/graphics/src/graphics/Geometry.cpp @@ -32,6 +32,13 @@ Mesh::Mesh(const Mesh& mesh) : Mesh::~Mesh() { } +void Mesh::setVertexFormatAndStream(const gpu::Stream::FormatPointer& vf, const gpu::BufferStreamPointer& vbs) { + _vertexFormat = vf; + _vertexStream = (*vbs); + + _vertexBuffer = BufferView(vbs->getBuffers()[0], vbs->getOffsets()[0], vbs->getBuffers()[0]->getSize(), vbs->getStrides()[0], gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); +} + void Mesh::setVertexBuffer(const BufferView& buffer) { _vertexBuffer = buffer; evalVertexFormat(); diff --git a/libraries/graphics/src/graphics/Geometry.h b/libraries/graphics/src/graphics/Geometry.h index eddfdbd1b6..20018ba71b 100755 --- a/libraries/graphics/src/graphics/Geometry.h +++ b/libraries/graphics/src/graphics/Geometry.h @@ -59,6 +59,9 @@ public: void removeAttribute(Slot slot); const BufferView getAttributeBuffer(int attrib) const; + // Force vertex stream and Vertex format + void setVertexFormatAndStream(const gpu::Stream::FormatPointer& vf, const gpu::BufferStreamPointer& vbs); + // Stream format const gpu::Stream::FormatPointer getVertexFormat() const { return _vertexFormat; } From 06af7b8729595d90ee8dec8dd86d74e7130763a6 Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Mon, 3 Sep 2018 07:58:39 -0400 Subject: [PATCH 205/744] Fixed typo on DQT_CMAKE_PREFIX_PATH --- BUILD_LINUX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 0daef5ae05..55e7260cd1 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -66,7 +66,7 @@ cd hifi/build Prepare makefiles: ```bash -cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10/gcc_64/lib/cmake .. +cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10.1/gcc_64/lib/cmake .. ``` Start compilation and get a cup of coffee: From 229a3bba90e0ed9279e0064067f5353860cfbe2e Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Mon, 3 Sep 2018 08:00:16 -0400 Subject: [PATCH 206/744] Added zlib1g-dev to required dependencies --- BUILD_LINUX.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 55e7260cd1..48e5a8efc1 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -6,7 +6,7 @@ Please read the [general build guide](BUILD.md) for information on dependencies Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required: - libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev + libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev zlib1g-dev ## Ubuntu 16.04 specific build guide @@ -20,7 +20,7 @@ sudo dpkg -i hifiqt5.10.1_5.10.1_amd64.deb Install build dependencies: ```bash -sudo apt-get install libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev +sudo apt-get install libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev zlib1g-dev ``` To compile interface in a server you must install: From f509dc4af4f9ef5c95640cb7bc680c2071166efb Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Mon, 3 Sep 2018 08:11:15 -0400 Subject: [PATCH 207/744] Added Ubuntu 18.04 step to add universe repository --- BUILD_LINUX.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 48e5a8efc1..421e81c8b8 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -8,7 +8,14 @@ Should you choose not to install Qt5 via a package manager that handles dependen libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev zlib1g-dev -## Ubuntu 16.04 specific build guide +## Ubuntu 16.04/18.04 specific build guide + +### Ubuntu 18.04 only +Add the universe repository (not enabled by default on the server edition): +```bash +sudo add-apt-repository universe +sudo apt-get update +``` ### Prepare environment hifiqt5.10.1 @@ -33,6 +40,7 @@ Install build tools: sudo apt install cmake ``` + ### Get code and checkout the tag you need Clone this repository: From 03051c0916b48814c92664647b69c2b383e66ab1 Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Mon, 3 Sep 2018 08:16:50 -0400 Subject: [PATCH 208/744] Revised Qt statement and Improved 18.04 step format --- BUILD_LINUX.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 421e81c8b8..c7351d4834 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -11,15 +11,15 @@ Should you choose not to install Qt5 via a package manager that handles dependen ## Ubuntu 16.04/18.04 specific build guide ### Ubuntu 18.04 only -Add the universe repository (not enabled by default on the server edition): +Add the universe repository: +_(This is not enabled by default on the server edition)_ ```bash sudo add-apt-repository universe sudo apt-get update ``` ### Prepare environment -hifiqt5.10.1 -Install qt: +Install Qt 5.10.1: ```bash wget http://debian.highfidelity.com/pool/h/hi/hifiqt5.10.1_5.10.1_amd64.deb sudo dpkg -i hifiqt5.10.1_5.10.1_amd64.deb From b0fe721af19902cccb5a22c533d57f57b497e7fb Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Mon, 3 Sep 2018 08:22:58 -0400 Subject: [PATCH 209/744] Revised checkout tag and removed download page mention --- BUILD_LINUX.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index c7351d4834..8b1f77e538 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -56,12 +56,7 @@ git tags Then checkout last tag with: ```bash -git checkout tags/RELEASE-6819 -``` - -Or go to the highfidelity download page (https://highfidelity.com/download) to get the release version. For example, if there is a BETA 6731 type: -```bash -git checkout tags/RELEASE-6731 +git checkout tags/v0.71.0 ``` ### Compiling From 143b4a61dd2821699249ffd8faca60abd88a00a5 Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Mon, 3 Sep 2018 08:25:45 -0400 Subject: [PATCH 210/744] Revised server compilation statement and grammer error --- BUILD_LINUX.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 8b1f77e538..e95ee2f4d4 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -72,12 +72,17 @@ Prepare makefiles: cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10.1/gcc_64/lib/cmake .. ``` -Start compilation and get a cup of coffee: +Start compilation of the server and get a cup of coffee: ```bash -make domain-server assignment-client interface +make domain-server assignment-client ``` -In a server does not make sense to compile interface +To compile interferace: +```bash +make interface +``` + +In a server, it does not make sense to compile interface ### Running the software From 26cb2409b95bafe86e5766b61ca9729bdebcef0d Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Mon, 3 Sep 2018 08:53:20 -0400 Subject: [PATCH 211/744] Added additional export QT_CMAKE_PREFIX_PATH related to BUILD_LINUX.md --- BUILD.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BUILD.md b/BUILD.md index 4a0274cea6..df3f18cf51 100644 --- a/BUILD.md +++ b/BUILD.md @@ -46,6 +46,7 @@ This can either be entered directly into your shell session before you build or The path it needs to be set to will depend on where and how Qt5 was installed. e.g. + export QT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10.1/gcc_64/lib/cmake export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.10.1/clang_64/lib/cmake/ export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.10.1/lib/cmake export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake From 8314386426cf494d6cfe6d0ebd378adc8bde0e84 Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Mon, 3 Sep 2018 08:55:15 -0400 Subject: [PATCH 212/744] Revised all apt statements to apt-get for consistency --- BUILD_LINUX.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index e95ee2f4d4..019b19ff8c 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -32,12 +32,12 @@ sudo apt-get install libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-de To compile interface in a server you must install: ```bash -sudo apt -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 libxcomposite1 libxtst6 libxslt1.1 +sudo apt-get -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 libxcomposite1 libxtst6 libxslt1.1 ``` Install build tools: ```bash -sudo apt install cmake +sudo apt-get install cmake ``` @@ -101,4 +101,4 @@ Running interface: ./interface/interface ``` -Go to localhost in running interface. +Go to localhost in the running interface. From 94e8ee99f5cade6f6f9a9bcf179c6b229a05cc37 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Mon, 3 Sep 2018 13:06:49 -0700 Subject: [PATCH 213/744] AVX2 implementation of interpolate() --- libraries/audio/src/AudioHRTF.cpp | 6 +++++- libraries/audio/src/avx2/AudioHRTF_avx2.cpp | 22 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index eea01b703b..b52f624620 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -477,8 +477,12 @@ static void crossfade_4x2(float* src, float* dst, const float* win, int numFrame (*f)(src, dst, win, numFrames); // dispatch } +void interpolate_AVX2(const float* src0, const float* src1, float* dst, float frac, float gain); + static void interpolate(const float* src0, const float* src1, float* dst, float frac, float gain) { - interpolate_SSE(src0, src1, dst, frac, gain); + + static auto f = cpuSupportsAVX2() ? interpolate_AVX2 : interpolate_SSE; + (*f)(src0, src1, dst, frac, gain); // dispatch } #else // portable reference code diff --git a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp index b82098b72f..aadbb2d0cd 100644 --- a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp +++ b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp @@ -226,4 +226,26 @@ void crossfade_4x2_AVX2(float* src, float* dst, const float* win, int numFrames) _mm256_zeroupper(); } +// linear interpolation with gain +void interpolate_AVX2(const float* src0, const float* src1, float* dst, float frac, float gain) { + + __m256 f0 = _mm256_set1_ps(gain * (1.0f - frac)); + __m256 f1 = _mm256_set1_ps(gain * frac); + + static_assert(HRTF_TAPS % 8 == 0, "HRTF_TAPS must be a multiple of 8"); + + for (int k = 0; k < HRTF_TAPS; k += 8) { + + __m256 x0 = _mm256_loadu_ps(&src0[k]); + __m256 x1 = _mm256_loadu_ps(&src1[k]); + + x0 = _mm256_mul_ps(f0, x0); + x0 = _mm256_fmadd_ps(f1, x1, x0); + + _mm256_storeu_ps(&dst[k], x0); + } + + _mm256_zeroupper(); +} + #endif From f5b7232f58906e82e24c22b12e778746f946b033 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Mon, 3 Sep 2018 14:00:30 -0700 Subject: [PATCH 214/744] Cleanup --- libraries/audio/src/AudioHRTF.cpp | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index b52f624620..b639301404 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -446,41 +446,32 @@ static void interpolate_SSE(const float* src0, const float* src1, float* dst, fl void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); +void interleave_4x4_AVX2(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames); +void biquad2_4x4_AVX2(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames); +void crossfade_4x2_AVX2(float* src, float* dst, const float* win, int numFrames); +void interpolate_AVX2(const float* src0, const float* src1, float* dst, float frac, float gain); static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) { - static auto f = cpuSupportsAVX512() ? FIR_1x4_AVX512 : (cpuSupportsAVX2() ? FIR_1x4_AVX2 : FIR_1x4_SSE); (*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch } -void interleave_4x4_AVX2(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames); - static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { - static auto f = cpuSupportsAVX2() ? interleave_4x4_AVX2 : interleave_4x4_SSE; (*f)(src0, src1, src2, src3, dst, numFrames); // dispatch } -void biquad2_4x4_AVX2(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames); - static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { - static auto f = cpuSupportsAVX2() ? biquad2_4x4_AVX2 : biquad2_4x4_SSE; (*f)(src, dst, coef, state, numFrames); // dispatch } -void crossfade_4x2_AVX2(float* src, float* dst, const float* win, int numFrames); - static void crossfade_4x2(float* src, float* dst, const float* win, int numFrames) { - static auto f = cpuSupportsAVX2() ? crossfade_4x2_AVX2 : crossfade_4x2_SSE; (*f)(src, dst, win, numFrames); // dispatch } -void interpolate_AVX2(const float* src0, const float* src1, float* dst, float frac, float gain); - static void interpolate(const float* src0, const float* src1, float* dst, float frac, float gain) { - static auto f = cpuSupportsAVX2() ? interpolate_AVX2 : interpolate_SSE; (*f)(src0, src1, dst, frac, gain); // dispatch } From b5f1c82e61ca6994736dd7f3f04a089f6c241d1b Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Mon, 3 Sep 2018 18:21:04 -0400 Subject: [PATCH 215/744] Corrected interferace to interface --- BUILD_LINUX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 019b19ff8c..1ee3d2b7c8 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -77,7 +77,7 @@ Start compilation of the server and get a cup of coffee: make domain-server assignment-client ``` -To compile interferace: +To compile interface: ```bash make interface ``` From 49e48f5ea0ac96da722b1849332020117e7e3202 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 31 Aug 2018 16:00:27 -0700 Subject: [PATCH 216/744] enable workload regulation by default --- libraries/workload/src/workload/ViewTask.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/workload/src/workload/ViewTask.h b/libraries/workload/src/workload/ViewTask.h index 7c1e4944f5..207bc04276 100644 --- a/libraries/workload/src/workload/ViewTask.h +++ b/libraries/workload/src/workload/ViewTask.h @@ -192,7 +192,7 @@ namespace workload { struct Data { - bool regulateViewRanges{ false }; // regulation is OFF by default + bool regulateViewRanges{ true }; // regulation is ON by default } data; struct DataExport { From c081fd75f7713abce64dbe6e1570428a8e86cf11 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Tue, 4 Sep 2018 09:08:35 -0700 Subject: [PATCH 217/744] update serverless content --- cmake/externals/serverless-content/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/externals/serverless-content/CMakeLists.txt b/cmake/externals/serverless-content/CMakeLists.txt index b4cacd8f90..12e2b7a4c6 100644 --- a/cmake/externals/serverless-content/CMakeLists.txt +++ b/cmake/externals/serverless-content/CMakeLists.txt @@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC72.zip - URL_MD5 b1d8faf9266bfbff88274a484911eb99 + URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC73.zip + URL_MD5 0c5edfb63cafb042311d3cf25261fbf2 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" From 24cf4adb44a55d79fe3a97f1a72dc4c2b238032f Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 4 Sep 2018 09:08:42 -0700 Subject: [PATCH 218/744] Fix MS17902: Prevent case where My Purchases says Open instead of Uninstall --- interface/src/commerce/QmlCommerce.cpp | 59 +++++++++++++++----------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 1c6600cf3f..06da18148f 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -40,11 +40,9 @@ QmlCommerce::QmlCommerce() { connect(ledger.data(), &Ledger::transferAssetToUsernameResult, this, &QmlCommerce::transferAssetToUsernameResult); connect(ledger.data(), &Ledger::availableUpdatesResult, this, &QmlCommerce::availableUpdatesResult); connect(ledger.data(), &Ledger::updateItemResult, this, &QmlCommerce::updateItemResult); - + auto accountManager = DependencyManager::get(); - connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { - setPassphrase(""); - }); + connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { setPassphrase(""); }); _appsPath = PathUtils::getAppDataPath() + "Apps/"; } @@ -105,7 +103,11 @@ void QmlCommerce::balance() { } } -void QmlCommerce::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) { +void QmlCommerce::inventory(const QString& editionFilter, + const QString& typeFilter, + const QString& titleFilter, + const int& page, + const int& perPage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); @@ -166,24 +168,30 @@ void QmlCommerce::certificateInfo(const QString& certificateId) { ledger->certificateInfo(certificateId); } -void QmlCommerce::transferAssetToNode(const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage) { +void QmlCommerce::transferAssetToNode(const QString& nodeID, + const QString& certificateID, + const int& amount, + const QString& optionalMessage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList keys = wallet->listPublicKeys(); if (keys.count() == 0) { - QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } }; return emit transferAssetToNodeResult(result); } QString key = keys[0]; ledger->transferAssetToNode(key, nodeID, certificateID, amount, optionalMessage); } -void QmlCommerce::transferAssetToUsername(const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage) { +void QmlCommerce::transferAssetToUsername(const QString& username, + const QString& certificateID, + const int& amount, + const QString& optionalMessage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList keys = wallet->listPublicKeys(); if (keys.count() == 0) { - QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } }; return emit transferAssetToUsernameResult(result); } QString key = keys[0]; @@ -194,10 +202,7 @@ void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& cert auto ledger = DependencyManager::get(); ledger->updateLocation(certificateID, DependencyManager::get()->getPlaceName(), true); qApp->replaceDomainContent(itemHref); - QJsonObject messageProperties = { - { "status", "SuccessfulRequestToReplaceContent" }, - { "content_set_url", itemHref } - }; + QJsonObject messageProperties = { { "status", "SuccessfulRequestToReplaceContent" }, { "content_set_url", itemHref } }; UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties); emit contentSetChanged(itemHref); @@ -214,10 +219,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) { QDir directory(_appsPath); QStringList apps = directory.entryList(QStringList("*.app.json")); - foreach(QString appFileName, apps) { - installedAppsFromMarketplace += appFileName; - installedAppsFromMarketplace += ","; - + foreach (QString appFileName, apps) { // If we were supplied a "justInstalledAppID" argument, that means we're entering this function // to get the new list of installed apps immediately after installing an app. // In that case, the app we installed may not yet have its associated script running - @@ -243,10 +245,12 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) { // delete the .app.json from the user's local disk. if (!runningScripts.contains(scriptURL)) { if (!appFile.remove()) { - qCWarning(commerce) - << "Couldn't delete local .app.json file (app's script isn't running). App filename is:" - << appFileName; + qCWarning(commerce) << "Couldn't delete local .app.json file (app's script isn't running). App filename is:" + << appFileName; } + } else { + installedAppsFromMarketplace += appFileName; + installedAppsFromMarketplace += ","; } } else { qCDebug(commerce) << "Couldn't open local .app.json file for reading."; @@ -317,7 +321,9 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) { // Read from the file to know what .js script to stop QFile appFile(_appsPath + "/" + appHref.fileName()); if (!appFile.open(QIODevice::ReadOnly)) { - qCDebug(commerce) << "Couldn't open local .app.json file for deletion. Cannot continue with app uninstallation. App filename is:" << appHref.fileName(); + qCDebug(commerce) + << "Couldn't open local .app.json file for deletion. Cannot continue with app uninstallation. App filename is:" + << appHref.fileName(); return false; } QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll()); @@ -325,13 +331,15 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) { QString scriptUrl = appFileJsonObject["scriptURL"].toString(); if (!DependencyManager::get()->stopScript(scriptUrl.trimmed(), false)) { - qCWarning(commerce) << "Couldn't stop script during app uninstall. Continuing anyway. ScriptURL is:" << scriptUrl.trimmed(); + qCWarning(commerce) << "Couldn't stop script during app uninstall. Continuing anyway. ScriptURL is:" + << scriptUrl.trimmed(); } // Delete the .app.json from the filesystem // remove() closes the file first. if (!appFile.remove()) { - qCWarning(commerce) << "Couldn't delete local .app.json file during app uninstall. Continuing anyway. App filename is:" << appHref.fileName(); + qCWarning(commerce) << "Couldn't delete local .app.json file during app uninstall. Continuing anyway. App filename is:" + << appHref.fileName(); } QFileInfo appFileInfo(appFile); @@ -352,7 +360,8 @@ bool QmlCommerce::openApp(const QString& itemHref) { QJsonObject appFileJsonObject = appFileJsonDocument.object(); QString homeUrl = appFileJsonObject["homeURL"].toString(); - auto tablet = dynamic_cast(DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); + auto tablet = dynamic_cast( + DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); if (homeUrl.contains(".qml", Qt::CaseInsensitive)) { tablet->loadQMLSource(homeUrl); } else if (homeUrl.contains(".html", Qt::CaseInsensitive)) { @@ -377,7 +386,7 @@ void QmlCommerce::updateItem(const QString& certificateId) { auto wallet = DependencyManager::get(); QStringList keys = wallet->listPublicKeys(); if (keys.count() == 0) { - QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } }; return emit updateItemResult(result); } QString key = keys[0]; From 761e8a3f957c2cff43bccd1c8f3e97b6120c5a08 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Tue, 4 Sep 2018 09:19:24 -0700 Subject: [PATCH 219/744] fixing warnings --- libraries/fbx/src/FBXReader_Mesh.cpp | 6 +++--- libraries/graphics/src/graphics/Geometry.cpp | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index 13a2c697a0..e6d9321fb5 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -585,7 +585,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { FBXMesh& fbxMesh = extractedMesh; graphics::MeshPointer mesh(new graphics::Mesh()); - bool blendShapes = !fbxMesh.blendshapes.empty(); + // bool blendShapes = !fbxMesh.blendshapes.empty(); int numVerts = extractedMesh.vertices.size(); // Grab the vertices in a buffer @@ -619,7 +619,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { qWarning() << "Unexpected tangents in " << url; } const auto normalsAndTangentsSize = normalsSize + tangentsSize; - const int normalsAndTangentsStride = 2 * sizeof(NormalType); + // const int normalsAndTangentsStride = 2 * sizeof(NormalType); const int colorsSize = fbxMesh.colors.size() * sizeof(ColorType); // Texture coordinates are stored in 2 half floats const int texCoordsSize = fbxMesh.texCoords.size() * sizeof(vec2h); @@ -635,7 +635,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // Normals and tangents are interleaved const int normalsOffset = 0; - const int tangentsOffset = normalsOffset + sizeof(NormalType); + // const int tangentsOffset = normalsOffset + sizeof(NormalType); const int totalNTSize = normalsOffset + normalsSize + tangentsSize; //const int colorsOffset = normalsOffset + normalsSize + tangentsSize; diff --git a/libraries/graphics/src/graphics/Geometry.cpp b/libraries/graphics/src/graphics/Geometry.cpp index 3f5f484b08..2fbe3708fd 100755 --- a/libraries/graphics/src/graphics/Geometry.cpp +++ b/libraries/graphics/src/graphics/Geometry.cpp @@ -36,7 +36,9 @@ void Mesh::setVertexFormatAndStream(const gpu::Stream::FormatPointer& vf, const _vertexFormat = vf; _vertexStream = (*vbs); - _vertexBuffer = BufferView(vbs->getBuffers()[0], vbs->getOffsets()[0], vbs->getBuffers()[0]->getSize(), vbs->getStrides()[0], gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + auto attrib = _vertexFormat->getAttribute(gpu::Stream::POSITION); + _vertexBuffer = BufferView(vbs->getBuffers()[attrib._channel], vbs->getOffsets()[attrib._channel], vbs->getBuffers()[attrib._channel]->getSize(), + (gpu::uint16) vbs->getStrides()[attrib._channel], attrib._element); } void Mesh::setVertexBuffer(const BufferView& buffer) { From 3a034fdf66a4311d1e7231cdde608f2fef69d8f2 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Fri, 31 Aug 2018 13:14:15 -0700 Subject: [PATCH 220/744] HMD mode bug fix for avatars with no eyes Previously getCenterEyeCalibrationMat() would return the average eye position for an avatar, if the eyes are missing. Now we fall back to using the HeadCalibrationMat() and adding an offset from the head to the center of the eyes. This is more accurate for characters that don't quite have human proportions. --- interface/src/avatar/MyAvatar.cpp | 4 +++- libraries/shared/src/AvatarConstants.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 640c9821a0..930a1b9f81 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4313,7 +4313,8 @@ glm::mat4 MyAvatar::getCenterEyeCalibrationMat() const { auto centerEyeRot = Quaternions::Y_180; return createMatFromQuatAndPos(centerEyeRot, centerEyePos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_ROT, DEFAULT_AVATAR_MIDDLE_EYE_POS / getSensorToWorldScale()); + glm::mat4 headMat = getHeadCalibrationMat(); + return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_ROT, extractTranslation(headMat) + (DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET / getSensorToWorldScale())); } } @@ -4323,6 +4324,7 @@ glm::mat4 MyAvatar::getHeadCalibrationMat() const { if (headIndex >= 0) { auto headPos = getAbsoluteDefaultJointTranslationInObjectFrame(headIndex); auto headRot = getAbsoluteDefaultJointRotationInObjectFrame(headIndex); + return createMatFromQuatAndPos(headRot, headPos / getSensorToWorldScale()); } else { return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_ROT, DEFAULT_AVATAR_HEAD_POS / getSensorToWorldScale()); diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index c3e8a3f173..986d39e94d 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -44,7 +44,7 @@ const float DEFAULT_AVATAR_RIGHTHAND_MASS = 2.0f; // Used when avatar is missing joints... (avatar space) const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; -const glm::vec3 DEFAULT_AVATAR_MIDDLE_EYE_POS { 0.0f, 0.6f, 0.0f }; +const glm::vec3 DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET = { 0.0f, 0.06f, -0.09f }; const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.53f, 0.0f }; const glm::quat DEFAULT_AVATAR_HEAD_ROT { Quaternions::Y_180 }; const glm::vec3 DEFAULT_AVATAR_RIGHTARM_POS { -0.134824f, 0.396348f, -0.0515777f }; From 058115b79142c4b591c89cf9a9c6f64f0fcf0986 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 4 Sep 2018 10:38:18 -0700 Subject: [PATCH 221/744] Add guard for sentJointDataOut --- libraries/avatars/src/AvatarData.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index f2f4e2fd7d..3e822fd17a 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -600,7 +600,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - (*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose; + if (sentJointDataOut) { + (*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose; + } if (++validityBit == BITS_IN_BYTE) { *validityPosition++ = validity; @@ -651,7 +653,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - (*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose; + if (sentJointDataOut) { + (*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose; + } if (++validityBit == BITS_IN_BYTE) { *validityPosition++ = validity; From fff6a10bcd4ecc7a272ec8ac481fdc6b9a3b3586 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 4 Sep 2018 10:58:40 -0700 Subject: [PATCH 222/744] Remove some debug logging from safe landing additions --- interface/src/octree/SafeLanding.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 60b660f66a..cfb0a2c753 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -75,7 +75,6 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { // Only track entities with downloaded collision bodies. _trackedEntities.emplace(entityID, entity); - qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); } } } @@ -110,7 +109,6 @@ bool SafeLanding::isLoadSequenceComplete() { _initialEnd = INVALID_SEQUENCE; _entityTree = nullptr; EntityTreeRenderer::setEntityLoadingPriorityFunction(StandardPriority); - qCDebug(interfaceapp) << "Safe Landing: load sequence complete"; } return !_trackingEntities; From a7abebd7aac88535d4753def06fc6f88c929a533 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 4 Sep 2018 11:11:34 -0700 Subject: [PATCH 223/744] adding redirect json to post build in Mac --- interface/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index ded77b9013..cc9fe8ef4c 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -332,6 +332,10 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/resources/fonts" "${RESOURCES_DEV_DIR}/fonts" + # add redirect json to macOS builds. + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${PROJECT_SOURCE_DIR}/resources/serverless/redirect.json" + "${RESOURCES_DEV_DIR}/serverless/redirect.json" ) # call the fixup_interface macro to add required bundling commands for installation From ade58a52cc97fe06c2f4bb88c4b9d097636fd4b9 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 4 Sep 2018 10:33:01 -0700 Subject: [PATCH 224/744] reserve priority sort util vectors --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 1 + interface/src/avatar/AvatarManager.cpp | 5 +++-- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 1 + libraries/shared/src/PrioritySortUtil.h | 3 +++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 59c6db5dc4..c434d82116 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -329,6 +329,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge); + sortedAvatars.reserve(avatarsToSort.size()); // ignore or sort const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer(); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index bd98549510..e9486b9def 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -187,16 +187,17 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { AvatarSharedPointer _avatar; }; + auto avatarMap = getHashCopy(); + AvatarHash::iterator itr = avatarMap.begin(); const auto& views = qApp->getConicalViews(); PrioritySortUtil::PriorityQueue sortedAvatars(views, AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge); + sortedAvatars.reserve(avatarMap.size() - 1); // don't include MyAvatar // sort - auto avatarMap = getHashCopy(); - AvatarHash::iterator itr = avatarMap.begin(); while (itr != avatarMap.end()) { const auto& avatar = std::static_pointer_cast(*itr); // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index a363093083..3d782f69a7 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -382,6 +382,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene const auto& views = _viewState->getConicalViews(); PrioritySortUtil::PriorityQueue sortedRenderables(views); + sortedRenderables.reserve(_renderablesToUpdate.size()); { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index e0137b3d8c..8ded047212 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -100,6 +100,9 @@ namespace PrioritySortUtil { thing.setPriority(computePriority(thing)); _vector.push_back(thing); } + void reserve(size_t num) { + _vector.reserve(num); + } const std::vector& getSortedVector() { std::sort(_vector.begin(), _vector.end(), [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); }); return _vector; From b21fa1037f1ff54fcec00af5026238a8c19a4b20 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 4 Sep 2018 11:55:52 -0700 Subject: [PATCH 225/744] Code review feedback and bug fixes for calibraiton matrices --- interface/src/avatar/MyAvatar.cpp | 21 +++++++++---------- interface/src/avatar/MyAvatar.h | 2 +- libraries/controllers/src/controllers/Input.h | 20 +++++++++--------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 930a1b9f81..e4503b4e78 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4314,7 +4314,7 @@ glm::mat4 MyAvatar::getCenterEyeCalibrationMat() const { return createMatFromQuatAndPos(centerEyeRot, centerEyePos / getSensorToWorldScale()); } else { glm::mat4 headMat = getHeadCalibrationMat(); - return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_ROT, extractTranslation(headMat) + (DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET / getSensorToWorldScale())); + return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_ROT, extractTranslation(headMat) + DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); } } @@ -4327,7 +4327,7 @@ glm::mat4 MyAvatar::getHeadCalibrationMat() const { return createMatFromQuatAndPos(headRot, headPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_ROT, DEFAULT_AVATAR_HEAD_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_ROT, DEFAULT_AVATAR_HEAD_POS); } } @@ -4339,7 +4339,7 @@ glm::mat4 MyAvatar::getSpine2CalibrationMat() const { auto spine2Rot = getAbsoluteDefaultJointRotationInObjectFrame(spine2Index); return createMatFromQuatAndPos(spine2Rot, spine2Pos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_SPINE2_ROT, DEFAULT_AVATAR_SPINE2_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_SPINE2_ROT, DEFAULT_AVATAR_SPINE2_POS); } } @@ -4351,7 +4351,7 @@ glm::mat4 MyAvatar::getHipsCalibrationMat() const { auto hipsRot = getAbsoluteDefaultJointRotationInObjectFrame(hipsIndex); return createMatFromQuatAndPos(hipsRot, hipsPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_HIPS_ROT, DEFAULT_AVATAR_HIPS_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_HIPS_ROT, DEFAULT_AVATAR_HIPS_POS); } } @@ -4363,7 +4363,7 @@ glm::mat4 MyAvatar::getLeftFootCalibrationMat() const { auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex); return createMatFromQuatAndPos(leftFootRot, leftFootPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS); } } @@ -4375,11 +4375,10 @@ glm::mat4 MyAvatar::getRightFootCalibrationMat() const { auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex); return createMatFromQuatAndPos(rightFootRot, rightFootPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS); } } - glm::mat4 MyAvatar::getRightArmCalibrationMat() const { int rightArmIndex = _skeletonModel->getRig().indexOfJoint("RightArm"); if (rightArmIndex >= 0) { @@ -4387,7 +4386,7 @@ glm::mat4 MyAvatar::getRightArmCalibrationMat() const { auto rightArmRot = getAbsoluteDefaultJointRotationInObjectFrame(rightArmIndex); return createMatFromQuatAndPos(rightArmRot, rightArmPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS); } } @@ -4398,7 +4397,7 @@ glm::mat4 MyAvatar::getLeftArmCalibrationMat() const { auto leftArmRot = getAbsoluteDefaultJointRotationInObjectFrame(leftArmIndex); return createMatFromQuatAndPos(leftArmRot, leftArmPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS); } } @@ -4409,7 +4408,7 @@ glm::mat4 MyAvatar::getRightHandCalibrationMat() const { auto rightHandRot = getAbsoluteDefaultJointRotationInObjectFrame(rightHandIndex); return createMatFromQuatAndPos(rightHandRot, rightHandPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTHAND_ROT, DEFAULT_AVATAR_RIGHTHAND_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTHAND_ROT, DEFAULT_AVATAR_RIGHTHAND_POS); } } @@ -4420,7 +4419,7 @@ glm::mat4 MyAvatar::getLeftHandCalibrationMat() const { auto leftHandRot = getAbsoluteDefaultJointRotationInObjectFrame(leftHandIndex); return createMatFromQuatAndPos(leftHandRot, leftHandPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTHAND_ROT, DEFAULT_AVATAR_LEFTHAND_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTHAND_ROT, DEFAULT_AVATAR_LEFTHAND_POS); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 8121b99e55..06267b3819 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1034,7 +1034,7 @@ public: virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; - // all calibration matrices are in absolute avatar space. + // all calibration matrices are in absolute sensor space. glm::mat4 getCenterEyeCalibrationMat() const; glm::mat4 getHeadCalibrationMat() const; glm::mat4 getSpine2CalibrationMat() const; diff --git a/libraries/controllers/src/controllers/Input.h b/libraries/controllers/src/controllers/Input.h index 3ca4076de2..3c01ee0942 100644 --- a/libraries/controllers/src/controllers/Input.h +++ b/libraries/controllers/src/controllers/Input.h @@ -19,16 +19,16 @@ struct InputCalibrationData { glm::mat4 sensorToWorldMat; // sensor to world glm::mat4 avatarMat; // avatar to world glm::mat4 hmdSensorMat; // hmd pos and orientation in sensor space - glm::mat4 defaultCenterEyeMat; // default pose for the center of the eyes in avatar space. - glm::mat4 defaultHeadMat; // default pose for head joint in avatar space - glm::mat4 defaultSpine2; // default pose for spine2 joint in avatar space - glm::mat4 defaultHips; // default pose for hips joint in avatar space - glm::mat4 defaultLeftFoot; // default pose for leftFoot joint in avatar space - glm::mat4 defaultRightFoot; // default pose for rightFoot joint in avatar space - glm::mat4 defaultRightArm; // default pose for rightArm joint in avatar space - glm::mat4 defaultLeftArm; // default pose for leftArm joint in avatar space - glm::mat4 defaultRightHand; // default pose for rightHand joint in avatar space - glm::mat4 defaultLeftHand; // default pose for leftHand joint in avatar space + glm::mat4 defaultCenterEyeMat; // default pose for the center of the eyes in sensor space. + glm::mat4 defaultHeadMat; // default pose for head joint in sensor space + glm::mat4 defaultSpine2; // default pose for spine2 joint in sensor space + glm::mat4 defaultHips; // default pose for hips joint in sensor space + glm::mat4 defaultLeftFoot; // default pose for leftFoot joint in sensor space + glm::mat4 defaultRightFoot; // default pose for rightFoot joint in sensor space + glm::mat4 defaultRightArm; // default pose for rightArm joint in sensor space + glm::mat4 defaultLeftArm; // default pose for leftArm joint in sensor space + glm::mat4 defaultRightHand; // default pose for rightHand joint in sensor space + glm::mat4 defaultLeftHand; // default pose for leftHand joint in sensor space }; enum class ChannelType { From dff8292216aed7a534b8725fe843ec72af7b88e0 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 4 Sep 2018 12:50:25 -0700 Subject: [PATCH 226/744] fix framebuffer debug --- .../render-utils/src/DebugDeferredBuffer.cpp | 86 +++++++------------ .../src/debug_deferred_buffer.slf | 12 +-- .../src/render-utils/ShaderConstants.h | 20 +---- 3 files changed, 36 insertions(+), 82 deletions(-) diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index cab95e50be..0e61b36280 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -38,33 +38,6 @@ void DebugDeferredBufferConfig::setMode(int newMode) { emit dirty(); } -#if 0 -enum TextureSlot { - Albedo = 0, - Normal, - Specular, - Depth, - Lighting, - Shadow, - LinearDepth, - HalfLinearDepth, - HalfNormal, - Curvature, - DiffusedCurvature, - Scattering, - AmbientOcclusion, - AmbientOcclusionBlurred, - Velocity, -}; - -enum ParamSlot { - CameraCorrection = 0, - DeferredFrameTransform, - ShadowTransform, - DebugParametersBuffer -}; -#endif - static const std::string DEFAULT_ALBEDO_SHADER{ "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" @@ -178,19 +151,19 @@ static const std::string DEFAULT_SHADOW_CASCADE_SHADER{ static const std::string DEFAULT_LINEAR_DEPTH_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(1.0 - texture(linearDepthMap, uv).x * 0.01), 1.0);" + " return vec4(vec3(1.0 - texture(debugTexture0, uv).x * 0.01), 1.0);" "}" }; static const std::string DEFAULT_HALF_LINEAR_DEPTH_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(1.0 - texture(halfLinearDepthMap, uv).x * 0.01), 1.0);" + " return vec4(vec3(1.0 - texture(debugTexture0, uv).x * 0.01), 1.0);" " }" }; static const std::string DEFAULT_HALF_NORMAL_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(texture(halfNormalMap, uv).xyz), 1.0);" + " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);" " }" }; @@ -240,27 +213,27 @@ static const std::string DEFAULT_CURVATURE_OCCLUSION_SHADER{ static const std::string DEFAULT_DEBUG_SCATTERING_SHADER{ "vec4 getFragmentColor() {" - " return vec4(pow(vec3(texture(scatteringMap, uv).xyz), vec3(1.0 / 2.2)), 1.0);" - // " return vec4(vec3(texture(scatteringMap, uv).xyz), 1.0);" + " return vec4(pow(vec3(texture(debugTexture0, uv).xyz), vec3(1.0 / 2.2)), 1.0);" + // " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);" " }" }; static const std::string DEFAULT_AMBIENT_OCCLUSION_SHADER{ "vec4 getFragmentColor() {" " return vec4(vec3(texture(obscuranceMap, uv).x), 1.0);" - // When drawing color " return vec4(vec3(texture(occlusionMap, uv).xyz), 1.0);" - // when drawing normal" return vec4(normalize(texture(occlusionMap, uv).xyz * 2.0 - vec3(1.0)), 1.0);" + // When drawing color " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);" + // when drawing normal" return vec4(normalize(texture(debugTexture0, uv).xyz * 2.0 - vec3(1.0)), 1.0);" " }" }; static const std::string DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(texture(occlusionBlurredMap, uv).xyz), 1.0);" + " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);" " }" }; static const std::string DEFAULT_VELOCITY_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec2(texture(velocityMap, uv).xy), 0.0, 1.0);" + " return vec4(vec2(texture(debugTexture0, uv).xy), 0.0, 1.0);" " }" }; @@ -463,13 +436,10 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(Textures::DeferredDepth, deferredFramebuffer->getPrimaryDepthTexture()); batch.setResourceTexture(Textures::DeferredLighting, deferredFramebuffer->getLightingTexture()); } - if (velocityFramebuffer) { - batch.setResourceTexture(Textures::DebugVelocity, velocityFramebuffer->getVelocityTexture()); + if (velocityFramebuffer && _mode == VelocityMode) { + batch.setResourceTexture(Textures::DebugTexture0, velocityFramebuffer->getVelocityTexture()); } - // FIXME can't find the corresponding buffer - // batch.setUniformBuffer(UBOs:: DebugParametersBuffer, _parameters); - auto lightStage = renderContext->_scene->getStage(); assert(lightStage); assert(lightStage->getNumLights() > 0); @@ -479,12 +449,17 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(Textures::Shadow, globalShadow->map); batch.setUniformBuffer(UBOs::ShadowParams, globalShadow->getBuffer()); batch.setUniformBuffer(UBOs::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(UBOs::DebugDeferredParams, _parameters); } if (linearDepthTarget) { - batch.setResourceTexture(Textures::DebugDepth, linearDepthTarget->getLinearDepthTexture()); - batch.setResourceTexture(Textures::DebugHalfDepth, linearDepthTarget->getHalfLinearDepthTexture()); - batch.setResourceTexture(Textures::DebugHalfNormal, linearDepthTarget->getHalfNormalTexture()); + if (_mode == DepthMode) { + batch.setResourceTexture(Textures::DebugTexture0, linearDepthTarget->getLinearDepthTexture()); + } else if (_mode == HalfLinearDepthMode) { + batch.setResourceTexture(Textures::DebugTexture0, linearDepthTarget->getHalfLinearDepthTexture()); + } else if (_mode == HalfNormalMode) { + batch.setResourceTexture(Textures::DebugTexture0, linearDepthTarget->getHalfNormalTexture()); + } } if (surfaceGeometryFramebuffer) { batch.setResourceTexture(Textures::DeferredCurvature, surfaceGeometryFramebuffer->getCurvatureTexture()); @@ -492,9 +467,11 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I surfaceGeometryFramebuffer->getLowCurvatureTexture()); } if (ambientOcclusionFramebuffer) { - batch.setResourceTexture(Textures::DebugOcclusion, ambientOcclusionFramebuffer->getOcclusionTexture()); - batch.setResourceTexture(Textures::DebugOcclusionBlurred, - ambientOcclusionFramebuffer->getOcclusionBlurredTexture()); + if (_mode == AmbientOcclusionMode) { + batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getOcclusionTexture()); + } else if (_mode == AmbientOcclusionBlurredMode) { + batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getOcclusionBlurredTexture()); + } } const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); const glm::vec2 bottomLeft(_size.x, _size.y); @@ -505,19 +482,14 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(Textures::DeferredNormal, nullptr); batch.setResourceTexture(Textures::DeferredSpecular, nullptr); batch.setResourceTexture(Textures::DeferredDepth, nullptr); - batch.setResourceTexture(Textures::DeferredLighting, nullptr); - batch.setResourceTexture(Textures::Shadow, nullptr); - batch.setResourceTexture(Textures::DebugDepth, nullptr); - batch.setResourceTexture(Textures::DebugHalfDepth, nullptr); - batch.setResourceTexture(Textures::DebugHalfNormal, nullptr); - batch.setResourceTexture(Textures::DeferredCurvature, nullptr); batch.setResourceTexture(Textures::DeferredDiffusedCurvature, nullptr); + batch.setResourceTexture(Textures::DeferredLighting, nullptr); - batch.setResourceTexture(Textures::DebugOcclusion, nullptr); - batch.setResourceTexture(Textures::DebugOcclusionBlurred, nullptr); - - batch.setResourceTexture(Textures::DebugVelocity, nullptr); + for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { + batch.setResourceTexture(Textures::Shadow + i, nullptr); + } + batch.setResourceTexture(Textures::DebugTexture0, nullptr); }); } diff --git a/libraries/render-utils/src/debug_deferred_buffer.slf b/libraries/render-utils/src/debug_deferred_buffer.slf index aee04cba90..013640d910 100644 --- a/libraries/render-utils/src/debug_deferred_buffer.slf +++ b/libraries/render-utils/src/debug_deferred_buffer.slf @@ -16,14 +16,8 @@ <@include gpu/Color.slh@> <$declareColorWheel()$> -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_DEPTH) uniform sampler2D linearDepthMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_HALF_DEPTH) uniform sampler2D halfLinearDepthMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_HALF_NORMAL) uniform sampler2D halfNormalMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION) uniform sampler2D occlusionMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION_BLURRED) uniform sampler2D occlusionBlurredMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_SCATTERING) uniform sampler2D scatteringMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_VELOCITY) uniform sampler2D velocityMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_SHADOWS) uniform sampler2DArrayShadow shadowMaps; +layout(binding=RENDER_UTILS_DEBUG_TEXTURE0) uniform sampler2D debugTexture0; +layout(binding=RENDER_UTILS_TEXTURE_SHADOW) uniform sampler2DArrayShadow shadowMaps; <@include ShadowCore.slh@> @@ -31,7 +25,7 @@ layout(binding=RENDER_UTILS_TEXTURE_DEBUG_SHADOWS) uniform sampler2DArrayShadow <@include debug_deferred_buffer_shared.slh@> -layout(std140) uniform parametersBuffer { +layout(std140, binding=RENDER_UTILS_BUFFER_DEBUG_DEFERRED_PARAMS) uniform parametersBuffer { DebugParameters parameters; }; diff --git a/libraries/render-utils/src/render-utils/ShaderConstants.h b/libraries/render-utils/src/render-utils/ShaderConstants.h index afb195c240..6a88a62287 100644 --- a/libraries/render-utils/src/render-utils/ShaderConstants.h +++ b/libraries/render-utils/src/render-utils/ShaderConstants.h @@ -126,14 +126,8 @@ // Debugging #define RENDER_UTILS_BUFFER_DEBUG_SKYBOX 5 -#define RENDER_UTILS_TEXTURE_DEBUG_DEPTH 11 -#define RENDER_UTILS_TEXTURE_DEBUG_HALF_DEPTH 12 -#define RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION 13 -#define RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION_BLURRED 14 -#define RENDER_UTILS_TEXTURE_DEBUG_VELOCITY 15 -#define RENDER_UTILS_TEXTURE_DEBUG_SHADOWS 16 -#define RENDER_UTILS_TEXTURE_DEBUG_HALF_NORMAL 17 -#define RENDER_UTILS_TEXTURE_DEBUG_SCATTERING 18 +#define RENDER_UTILS_DEBUG_TEXTURE0 11 +#define RENDER_UTILS_BUFFER_DEBUG_DEFERRED_PARAMS 1 // @@ -174,6 +168,7 @@ enum Buffer { BloomParams = RENDER_UTILS_BUFFER_BLOOM_PARAMS, ToneMappingParams = RENDER_UTILS_BUFFER_TM_PARAMS, ShadowParams = RENDER_UTILS_BUFFER_SHADOW_PARAMS, + DebugDeferredParams = RENDER_UTILS_BUFFER_DEBUG_DEFERRED_PARAMS, }; } // namespace buffer @@ -212,14 +207,7 @@ enum Texture { BloomColor = RENDER_UTILS_TEXTURE_BLOOM_COLOR, ToneMappingColor = RENDER_UTILS_TEXTURE_TM_COLOR, TextFont = RENDER_UTILS_TEXTURE_TEXT_FONT, - DebugDepth = RENDER_UTILS_TEXTURE_DEBUG_DEPTH, - DebugHalfDepth = RENDER_UTILS_TEXTURE_DEBUG_HALF_DEPTH, - DebugOcclusion = RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION, - DebugOcclusionBlurred = RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION_BLURRED, - DebugVelocity = RENDER_UTILS_TEXTURE_DEBUG_VELOCITY, - DebugShadows = RENDER_UTILS_TEXTURE_DEBUG_SHADOWS, - DebugHalfNormal = RENDER_UTILS_TEXTURE_DEBUG_HALF_NORMAL, - DebugScattering = RENDER_UTILS_TEXTURE_DEBUG_SCATTERING, + DebugTexture0 = RENDER_UTILS_DEBUG_TEXTURE0, }; } // namespace texture From ec0b0ab464fcb12b0e01b86cb837470259be2069 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 4 Sep 2018 12:59:53 -0700 Subject: [PATCH 227/744] fix acceleration spread --- .../src/RenderableParticleEffectEntityItem.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 18c4921836..1a263fba79 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -187,12 +187,10 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa particle.basePosition = baseTransform.getTranslation(); // Position, velocity, and acceleration + glm::vec3 emitDirection; if (polarStart == 0.0f && polarFinish == 0.0f && emitDimensions.z == 0.0f) { // Emit along z-axis from position - - particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * Vectors::UNIT_Z); - particle.acceleration = emitAcceleration + randFloatInRange(-1.0f, 1.0f) * accelerationSpread; - + emitDirection = Vectors::UNIT_Z; } else { // Emit around point or from ellipsoid // - Distribute directions evenly around point @@ -210,7 +208,6 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat(); } - glm::vec3 emitDirection; if (emitDimensions == Vectors::ZERO) { // Point emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z; @@ -235,10 +232,10 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa )); particle.relativePosition += emitOrientation * emitPosition; } - - particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * emitDirection); - particle.acceleration = emitAcceleration + randFloatInRange(-1.0f, 1.0f) * accelerationSpread; } + particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * emitDirection); + particle.acceleration = emitAcceleration + + glm::vec3(randFloatInRange(-1.0f, 1.0f), randFloatInRange(-1.0f, 1.0f), randFloatInRange(-1.0f, 1.0f)) * accelerationSpread; return particle; } From 22a8164401080f8d55cc3f6ae70eedc2f35a14f6 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 4 Sep 2018 13:53:03 -0700 Subject: [PATCH 228/744] adding back refusal notifications + domain bounds/floor --- interface/resources/serverless/redirect.json | 1591 +++++++++--------- scripts/system/notifications.js | 2 +- 2 files changed, 833 insertions(+), 760 deletions(-) diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index fd81b6d433..72c4abc16b 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -1,37 +1,42 @@ { "DataVersion": 0, + "Paths": + { + "/": "/4,1.4,4/0,0.49544,0,0.868645" + }, "Entities": [ { "clientOnly": false, "collidesWith": "static,dynamic,kinematic,otherAvatar,", "collisionMask": 23, - "created": "2018-08-31T21:40:18Z", + "created": "2018-09-04T20:35:23Z", "dimensions": { - "blue": 0.8231174349784851, - "green": 1.8264780044555664, - "red": 1.2112245559692383, - "x": 1.2112245559692383, - "y": 1.8264780044555664, - "z": 0.8231174349784851 + "blue": 0.8660923838615417, + "green": 1.921838402748108, + "red": 1.2744625806808472, + "x": 1.2744625806808472, + "y": 1.921838402748108, + "z": 0.8660923838615417 }, - "id": "{1a8bf6e0-6f03-4761-9aba-1f624fae1236}", - "lastEdited": 1535751992686833, - "lastEditedBy": "{ace8f357-ad94-49e1-b4d0-39c4d5a936f3}", + "id": "{3889145e-9db2-46dc-9765-9ba7594457dc}", + "lastEdited": 1536093329670067, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, "name": "Try Again Zone", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { - "blue": 4.442525863647461, + "blue": 4.524896621704102, "green": 1.9289360046386719, - "red": 2.600449562072754, - "x": -1.399550437927246, - "y": -7.571063995361328, - "z": 0.44252586364746094 + "red": 2.893861770629883, + "x": 2.893861770629883, + "y": 1.9289360046386719, + "z": 4.524896621704102 }, "queryAACube": { - "scale": 2.3410699367523193, - "x": 1.4299145936965942, - "y": 0.7584010362625122, - "z": 3.2719907760620117 + "scale": 2.4632973670959473, + "x": 1.6622130870819092, + "y": 0.6972873210906982, + "z": 3.293247938156128 }, "rotation": { "w": 0.9743700623512268, @@ -39,7 +44,7 @@ "y": -0.22495104372501373, "z": 0 }, - "script": "file:///C:/Users/wayne/development/zoneTryAgainEntityScript.js", + "script": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/scripts/redirect/zoneTryAgainEntityScript.js", "shapeType": "box", "type": "Zone", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" @@ -48,33 +53,34 @@ "clientOnly": false, "collidesWith": "static,dynamic,kinematic,otherAvatar,", "collisionMask": 23, - "created": "2018-08-31T21:44:40Z", + "created": "2018-09-04T20:35:23Z", "dimensions": { - "blue": 0.7544999718666077, - "green": 1.8265000581741333, - "red": 1.0555000305175781, - "x": 1.0555000305175781, - "y": 1.8265000581741333, - "z": 0.7544999718666077 + "blue": 1.159199833869934, + "green": 2.8062009811401367, + "red": 1.6216505765914917, + "x": 1.6216505765914917, + "y": 2.8062009811401367, + "z": 1.159199833869934 }, - "id": "{eb439982-948a-4393-8e8c-fdd833e6d9a7}", - "lastEdited": 1535752005525769, - "lastEditedBy": "{ace8f357-ad94-49e1-b4d0-39c4d5a936f3}", + "id": "{8facc8e1-bc17-4460-8a9c-868b07c1b387}", + "lastEdited": 1536093329669913, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, "name": "Back Zone", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { - "blue": 2.538674831390381, + "blue": 1.8632707595825195, "green": 1.8022732734680176, - "red": 3.5101304054260254, - "x": -0.4898695945739746, - "y": -7.697726726531982, - "z": -1.4613251686096191 + "red": 3.3276727199554443, + "x": 3.3276727199554443, + "y": 1.8022732734680176, + "z": 1.8632707595825195 }, "queryAACube": { - "scale": 2.2404136657714844, - "x": 2.389923572540283, - "y": 0.6820664405822754, - "z": 1.4184679985046387 + "scale": 3.4421300888061523, + "x": 1.6066076755523682, + "y": 0.0812082290649414, + "z": 0.14220571517944336 }, "rotation": { "w": 0.9304176568984985, @@ -82,186 +88,11 @@ "y": -0.36650121212005615, "z": 0 }, - "script": "file:///C:/Users/wayne/development/zoneBackEntityScript.js", + "script": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/scripts/redirect/zoneBackEntityScript.js", "shapeType": "box", "type": "Zone", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, - { - "clientOnly": false, - "created": "2018-08-31T21:35:56Z", - "dimensions": { - "blue": 14.40000057220459, - "green": 14.40000057220459, - "red": 14.40000057220459, - "x": 14.40000057220459, - "y": 14.40000057220459, - "z": 14.40000057220459 - }, - "id": "{dbae0b3a-a7ff-4eb1-9177-fd1f20b3cec8}", - "lastEdited": 1535751789461074, - "lastEditedBy": "{ace8f357-ad94-49e1-b4d0-39c4d5a936f3}", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 2.3440732955932617, - "green": 1.7684326171875, - "red": 1.8812973499298096, - "x": -2.1187026500701904, - "y": -7.7315673828125, - "z": -1.6559267044067383 - }, - "queryAACube": { - "scale": 24.9415340423584, - "x": -10.589469909667969, - "y": -10.7023344039917, - "z": -10.126693725585938 - }, - "rotation": { - "w": 0.8697794675827026, - "x": -1.52587890625e-05, - "y": 0.4933699369430542, - "z": -4.57763671875e-05 - }, - "shapeType": "box", - "skyboxMode": "enabled", - "type": "Zone", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, - { - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 0 - }, - "created": "2018-08-31T21:35:56Z", - "dimensions": { - "blue": 11.117486953735352, - "green": 3.580313205718994, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 3.580313205718994, - "z": 11.117486953735352 - }, - "id": "{c92b026b-e13c-448a-903c-2773f9fdc2e8}", - "lastEdited": 1535751961512543, - "lastEditedBy": "{ace8f357-ad94-49e1-b4d0-39c4d5a936f3}", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 1.9063844680786133, - "green": 1.15850830078125, - "red": 0.16632509231567383, - "x": -3.833674907684326, - "y": -8.34149169921875, - "z": -2.0936155319213867 - }, - "queryAACube": { - "scale": 11.681488037109375, - "x": -5.674418926239014, - "y": -4.6822357177734375, - "z": -3.934359550476074 - }, - "rotation": { - "w": 0.9666743278503418, - "x": -4.57763671875e-05, - "y": -0.2560006380081177, - "z": 1.52587890625e-05 - }, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 0 - }, - "created": "2018-08-31T21:35:56Z", - "dimensions": { - "blue": 11.117486953735352, - "green": 3.580313205718994, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 3.580313205718994, - "z": 11.117486953735352 - }, - "id": "{cd72debb-75a6-420d-b39f-b903fb069798}", - "lastEdited": 1535751362199714, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 6.1806135177612305, - "green": 1.1588134765625, - "red": 1.4755167961120605, - "x": -2.5244832038879395, - "y": -8.3411865234375, - "z": 2.1806135177612305 - }, - "queryAACube": { - "scale": 11.681488037109375, - "x": -4.365227222442627, - "y": -4.6819305419921875, - "z": 0.33986949920654297 - }, - "rotation": { - "w": 0.8637980222702026, - "x": -4.57763671875e-05, - "y": 0.5038070678710938, - "z": -1.52587890625e-05 - }, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 0 - }, - "created": "2018-08-31T21:35:56Z", - "dimensions": { - "blue": 11.117486953735352, - "green": 3.580313205718994, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 3.580313205718994, - "z": 11.117486953735352 - }, - "id": "{abe300d5-32b8-4d0c-bf82-e78005b79b70}", - "lastEdited": 1535751362199153, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 5.268576622009277, - "green": 1.1588134765625, - "red": 6.100250244140625, - "x": 2.100250244140625, - "y": -8.3411865234375, - "z": 1.2685766220092773 - }, - "queryAACube": { - "scale": 11.681488037109375, - "x": 0.2595062255859375, - "y": -4.6819305419921875, - "z": -0.5721673965454102 - }, - "rotation": { - "w": 0.9662165641784668, - "x": -4.57763671875e-05, - "y": -0.2576791048049927, - "z": 1.52587890625e-05 - }, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, { "angularDamping": 0, "clientOnly": false, @@ -270,7 +101,7 @@ "green": 0, "red": 255 }, - "created": "2018-08-31T21:35:56Z", + "created": "2018-09-04T20:35:23Z", "dimensions": { "blue": 0.20000000298023224, "green": 0.20000000298023224, @@ -279,18 +110,19 @@ "y": 0.20000000298023224, "z": 0.20000000298023224 }, - "id": "{66757fda-b7ad-4858-83ab-724b01710cc2}", - "lastEdited": 1535751362198937, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "id": "{ed4142db-f3c9-441b-9091-cd1755b6aeb1}", + "lastEdited": 1536093329672493, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { "blue": 1.3116319179534912, "green": 0.15618896484375, "red": 2.705613613128662, - "x": -1.294386386871338, - "y": -9.34381103515625, - "z": -2.688368082046509 + "x": 2.705613613128662, + "y": 0.15618896484375, + "z": 1.3116319179534912 }, "queryAACube": { "scale": 0.3464101552963257, @@ -311,148 +143,6 @@ "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false }, - { - "angularDamping": 0, - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-08-31T21:35:56Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{24eda146-92f8-4e43-b084-fb555626427b}", - "lastEdited": 1535751362198263, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 1.3116319179534912, - "green": 0, - "red": 2.705613613128662, - "x": -1.294386386871338, - "y": -9.5, - "z": -2.688368082046509 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 2.5324084758758545, - "y": -0.17320507764816284, - "z": 1.1384267807006836 - }, - "rotation": { - "w": 0.9127794504165649, - "x": 0.2575265169143677, - "y": 0.15553522109985352, - "z": 0.2761729955673218 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "alpha": 0, - "alphaFinish": 0, - "alphaStart": 0.25, - "clientOnly": false, - "colorFinish": { - "blue": 0, - "green": 0, - "red": 0, - "x": 0, - "y": 0, - "z": 0 - }, - "colorStart": { - "blue": 255, - "green": 255, - "red": 255, - "x": 255, - "y": 255, - "z": 255 - }, - "created": "2018-08-31T21:35:56Z", - "dimensions": { - "blue": 13.24000072479248, - "green": 13.24000072479248, - "red": 13.24000072479248, - "x": 13.24000072479248, - "y": 13.24000072479248, - "z": 13.24000072479248 - }, - "emitAcceleration": { - "blue": 0, - "green": 0.10000000149011612, - "red": 0, - "x": 0, - "y": 0.10000000149011612, - "z": 0 - }, - "emitDimensions": { - "blue": 1, - "green": 1, - "red": 1, - "x": 1, - "y": 1, - "z": 1 - }, - "emitOrientation": { - "w": 1, - "x": -1.52587890625e-05, - "y": -1.52587890625e-05, - "z": -1.52587890625e-05 - }, - "emitRate": 6, - "emitSpeed": 0, - "emitterShouldTrail": true, - "id": "{b9a46e3e-f982-4a46-b38d-2a38e5cc5fbc}", - "lastEdited": 1535751362199964, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "lifespan": 10, - "maxParticles": 10, - "name": "Stars", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "particleRadius": 0.07000000029802322, - "polarFinish": 3.1415927410125732, - "position": { - "blue": 3.78922963142395, - "green": 0.5220947265625, - "red": 1.1928560733795166, - "x": -2.8071439266204834, - "y": -8.9779052734375, - "z": -0.2107703685760498 - }, - "queryAACube": { - "scale": 22.932353973388672, - "x": -10.273321151733398, - "y": -10.944082260131836, - "z": -7.676947593688965 - }, - "radiusFinish": 0, - "radiusStart": 0, - "rotation": { - "w": 0.996429443359375, - "x": -1.52587890625e-05, - "y": -0.08442819118499756, - "z": -4.57763671875e-05 - }, - "speedSpread": 0, - "spinFinish": null, - "spinStart": null, - "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", - "type": "ParticleEffect", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, { "clientOnly": false, "color": { @@ -460,7 +150,7 @@ "green": 0, "red": 0 }, - "created": "2018-08-31T21:35:56Z", + "created": "2018-09-04T20:35:23Z", "dimensions": { "blue": 11.117486953735352, "green": 3.580313205718994, @@ -469,17 +159,18 @@ "y": 3.580313205718994, "z": 11.117486953735352 }, - "id": "{e6897ca3-1c22-429f-a1b6-173ff47397a7}", - "lastEdited": 1535751362197848, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "id": "{d49f29ca-72b6-473d-917c-f7bf7d66f219}", + "lastEdited": 1536093329670344, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { "blue": 0, "green": 1.1583251953125, "red": 4.971565246582031, - "x": 0.9715652465820312, - "y": -8.3416748046875, - "z": -4 + "x": 4.971565246582031, + "y": 1.1583251953125, + "z": 0 }, "queryAACube": { "scale": 11.681488037109375, @@ -498,6 +189,47 @@ "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false }, + { + "clientOnly": false, + "created": "2018-09-04T20:35:23Z", + "dimensions": { + "blue": 0.06014331430196762, + "green": 2.582186460494995, + "red": 2.582186698913574, + "x": 2.582186698913574, + "y": 2.582186460494995, + "z": 0.06014331430196762 + }, + "id": "{6ccf519b-4e0b-4c23-a51b-305d0a64ab06}", + "lastEdited": 1536093329671943, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/oopsDialog.fbx", + "name": "Oops Dialog", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.45927095413208, + "green": 1.6763916015625, + "red": 0, + "x": 0, + "y": 1.6763916015625, + "z": 1.45927095413208 + }, + "queryAACube": { + "scale": 3.6522583961486816, + "x": -1.8261291980743408, + "y": -0.14973759651184082, + "z": -0.36685824394226074 + }, + "rotation": { + "w": 0.8684672117233276, + "x": -4.57763671875e-05, + "y": 0.4957197904586792, + "z": -7.62939453125e-05 + }, + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, { "angularDamping": 0, "clientOnly": false, @@ -506,7 +238,7 @@ "green": 0, "red": 255 }, - "created": "2018-08-31T21:35:56Z", + "created": "2018-09-04T20:35:23Z", "dimensions": { "blue": 0.20000000298023224, "green": 0.20000000298023224, @@ -515,18 +247,19 @@ "y": 0.20000000298023224, "z": 0.20000000298023224 }, - "id": "{16ca16ab-eee3-41b1-b583-159245aa2010}", - "lastEdited": 1535751362197055, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "id": "{625c95a4-1592-4996-bd48-7aa3fdbe6aee}", + "lastEdited": 1536093329671797, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { "blue": 1.3116319179534912, "green": 0.0772705078125, "red": 2.705613613128662, - "x": -1.294386386871338, - "y": -9.4227294921875, - "z": -2.688368082046509 + "x": 2.705613613128662, + "y": 0.0772705078125, + "z": 1.3116319179534912 }, "queryAACube": { "scale": 0.3464101552963257, @@ -549,7 +282,53 @@ }, { "clientOnly": false, - "created": "2018-08-31T21:35:56Z", + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-09-04T20:35:23Z", + "dimensions": { + "blue": 8.645400047302246, + "green": 0.20000000298023224, + "red": 20.025121688842773, + "x": 20.025121688842773, + "y": 0.20000000298023224, + "z": 8.645400047302246 + }, + "id": "{d356bb44-7888-476a-821e-961ae7d9330e}", + "lastEdited": 1536093329671479, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 4.846520900726318, + "green": 3.0651936531066895, + "red": 5.746071815490723, + "x": 5.746071815490723, + "y": 3.0651936531066895, + "z": 4.846520900726318 + }, + "queryAACube": { + "scale": 21.812576293945312, + "x": -5.160216331481934, + "y": -7.841094493865967, + "z": -6.059767246246338 + }, + "rotation": { + "w": 0.970295786857605, + "x": 0, + "y": -0.24192190170288086, + "z": 0 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-09-04T20:35:23Z", "dimensions": { "blue": 2.1097896099090576, "green": 0.04847164824604988, @@ -558,9 +337,10 @@ "y": 0.04847164824604988, "z": 2.1097896099090576 }, - "id": "{482ae172-c411-41fb-9a41-4215eef25478}", - "lastEdited": 1535751754378548, - "lastEditedBy": "{ace8f357-ad94-49e1-b4d0-39c4d5a936f3}", + "id": "{737f1b31-0342-4764-89fb-2ef7ddf83983}", + "lastEdited": 1536093329672351, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal2.fbx", "name": "Back", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", @@ -568,9 +348,9 @@ "blue": 1.5835940837860107, "green": 0.2467041015625, "red": 3.0345542430877686, - "x": -0.9654457569122314, - "y": -9.2532958984375, - "z": -2.4164059162139893 + "x": 3.0345542430877686, + "y": 0.2467041015625, + "z": 1.5835940837860107 }, "queryAACube": { "scale": 2.5651814937591553, @@ -584,97 +364,53 @@ "y": 0.4179598093032837, "z": -0.0001068115234375 }, - "script": "file:///C:/Users/wayne/development/backEntityScript.js", + "script": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/scripts/redirect/backEntityScript.js", "scriptTimestamp": 1535751754379, "shapeType": "static-mesh", "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, { - "clientOnly": false, - "created": "2018-08-31T21:35:56Z", - "dimensions": { - "blue": 9.030570983886719, - "green": 0.0719478651881218, - "red": 9.030570983886719, - "x": 9.030570983886719, - "y": 0.0719478651881218, - "z": 9.030570983886719 - }, - "id": "{33440bf8-f9ce-4d52-9b29-1b321e226982}", - "lastEdited": 1535751362197214, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/floor.fbx", - "name": "Floor", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 4.2324604988098145, - "green": 0.17547607421875, - "red": 4.802988529205322, - "x": 0.8029885292053223, - "y": -9.32452392578125, - "z": 0.23246049880981445 - }, - "queryAACube": { - "scale": 12.771358489990234, - "x": -1.582690715789795, - "y": -6.210203170776367, - "z": -2.1532187461853027 - }, - "rotation": { - "w": 0.8648051023483276, - "x": -1.52587890625e-05, - "y": 0.5020675659179688, - "z": -4.57763671875e-05 - }, - "shapeType": "simple-hull", - "type": "Model", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, - { - "angularDamping": 0, "clientOnly": false, "color": { "blue": 0, "green": 0, - "red": 255 + "red": 0 }, - "created": "2018-08-31T21:35:56Z", + "created": "2018-09-04T20:35:23Z", "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, + "blue": 11.117486953735352, + "green": 3.580313205718994, "red": 0.20000000298023224, "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 + "y": 3.580313205718994, + "z": 11.117486953735352 }, - "id": "{7639140e-cd6f-4f86-97f7-00a19f50253e}", - "lastEdited": 1535751362198721, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "name": "Particle Rotate", + "id": "{87f734c8-37bb-4ac0-b300-5028f7fe6f9d}", + "lastEdited": 1536093329670472, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { - "blue": 3.8216662406921387, - "green": 0, - "red": 1.2409718036651611, - "x": -2.759028196334839, - "y": -9.5, - "z": -0.17833375930786133 + "blue": 2.662257671356201, + "green": 1.1585893630981445, + "red": 1.493349552154541, + "x": 1.493349552154541, + "y": 1.1585893630981445, + "z": 2.662257671356201 }, "queryAACube": { - "scale": 0.3464101552963257, - "x": 1.0677666664123535, - "y": -0.17320507764816284, - "z": 3.648461103439331 + "scale": 11.681488037109375, + "x": -4.3473944664001465, + "y": -4.682154655456543, + "z": -3.1784863471984863 }, "rotation": { - "w": 0.6444342136383057, - "x": -0.08220034837722778, - "y": -0.6649118661880493, - "z": 0.3684900999069214 + "w": 0.9666743278503418, + "x": -4.57763671875e-05, + "y": -0.2560006380081177, + "z": 1.52587890625e-05 }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", @@ -682,7 +418,7 @@ }, { "clientOnly": false, - "created": "2018-08-31T21:35:56Z", + "created": "2018-09-04T20:35:23Z", "dimensions": { "blue": 2.1097896099090576, "green": 0.04847164824604988, @@ -691,9 +427,10 @@ "y": 0.04847164824604988, "z": 2.1097896099090576 }, - "id": "{7755bf99-b026-490d-a682-edadd3f65701}", - "lastEdited": 1535751900322535, - "lastEditedBy": "{ace8f357-ad94-49e1-b4d0-39c4d5a936f3}", + "id": "{9584bb57-b543-4d2b-af98-f3de618eef19}", + "lastEdited": 1536093329672070, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal1.fbx", "name": "Try Again", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", @@ -701,9 +438,9 @@ "blue": 3.946338653564453, "green": 0.2467041015625, "red": 1.6013128757476807, - "x": -2.3986871242523193, - "y": -9.2532958984375, - "z": -0.053661346435546875 + "x": 1.6013128757476807, + "y": 0.2467041015625, + "z": 3.946338653564453 }, "queryAACube": { "scale": 2.5651814937591553, @@ -717,251 +454,11 @@ "y": 0.5693598985671997, "z": -0.0001068115234375 }, - "script": "file:///C:/Users/wayne/development/tryAgainEntityScript.js", + "script": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/scripts/redirect/tryAgainEntityScript.js", "shapeType": "static-mesh", "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, - { - "angularDamping": 0, - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-08-31T21:35:56Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{81207b9c-e3f2-4ee1-bb81-0e14f37e4687}", - "lastEdited": 1535751362197613, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 3.8216662406921387, - "green": 0.15618896484375, - "red": 1.2409718036651611, - "x": -2.759028196334839, - "y": -9.34381103515625, - "z": -0.17833375930786133 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 1.0677666664123535, - "y": -0.017016112804412842, - "z": 3.648461103439331 - }, - "rotation": { - "w": 0.6747081279754639, - "x": -0.06532388925552368, - "y": -0.6342412233352661, - "z": 0.37175559997558594 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "alpha": 0, - "alphaFinish": 0, - "alphaStart": 0.25, - "clientOnly": false, - "colorFinish": { - "blue": 0, - "green": 0, - "red": 0, - "x": 0, - "y": 0, - "z": 0 - }, - "colorStart": { - "blue": 255, - "green": 255, - "red": 255, - "x": 255, - "y": 255, - "z": 255 - }, - "created": "2018-08-31T21:35:56Z", - "dimensions": { - "blue": 13.24000072479248, - "green": 13.24000072479248, - "red": 13.24000072479248, - "x": 13.24000072479248, - "y": 13.24000072479248, - "z": 13.24000072479248 - }, - "emitAcceleration": { - "blue": 0, - "green": 0.10000000149011612, - "red": 0, - "x": 0, - "y": 0.10000000149011612, - "z": 0 - }, - "emitDimensions": { - "blue": 1, - "green": 1, - "red": 1, - "x": 1, - "y": 1, - "z": 1 - }, - "emitOrientation": { - "w": 1, - "x": -1.52587890625e-05, - "y": -1.52587890625e-05, - "z": -1.52587890625e-05 - }, - "emitRate": 6, - "emitSpeed": 0, - "id": "{b8f11bf5-410e-4e35-b4c3-91c86c01351d}", - "lastEdited": 1535751362196710, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "lifespan": 10, - "maxParticles": 10, - "name": "Stars", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "particleRadius": 0.07000000029802322, - "polarFinish": 3.1415927410125732, - "position": { - "blue": 1.3712034225463867, - "green": 0.5220947265625, - "red": 2.6281180381774902, - "x": -1.3718819618225098, - "y": -8.9779052734375, - "z": -2.6287965774536133 - }, - "queryAACube": { - "scale": 22.932353973388672, - "x": -8.838058471679688, - "y": -10.944082260131836, - "z": -10.09497356414795 - }, - "radiusFinish": 0, - "radiusStart": 0, - "rotation": { - "w": 0.9852597713470459, - "x": -1.52587890625e-05, - "y": -0.17106890678405762, - "z": -7.62939453125e-05 - }, - "speedSpread": 0, - "spinFinish": null, - "spinStart": null, - "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", - "type": "ParticleEffect", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, - { - "alpha": 0, - "alphaFinish": 0, - "alphaStart": 1, - "clientOnly": false, - "color": { - "blue": 255, - "green": 205, - "red": 3 - }, - "colorFinish": { - "blue": 0, - "green": 0, - "red": 0, - "x": 0, - "y": 0, - "z": 0 - }, - "colorStart": { - "blue": 255, - "green": 204, - "red": 0, - "x": 0, - "y": 204, - "z": 255 - }, - "created": "2018-08-31T21:35:56Z", - "dimensions": { - "blue": 2.5, - "green": 2.5, - "red": 2.5, - "x": 2.5, - "y": 2.5, - "z": 2.5 - }, - "emitAcceleration": { - "blue": 0, - "green": 0, - "red": 0, - "x": 0, - "y": 0, - "z": 0 - }, - "emitDimensions": { - "blue": 1, - "green": 1, - "red": 1, - "x": 1, - "y": 1, - "z": 1 - }, - "emitOrientation": { - "w": 0.9993909597396851, - "x": 0.034897372126579285, - "y": -1.525880907138344e-05, - "z": -1.525880907138344e-05 - }, - "emitRate": 2, - "emitSpeed": 0, - "emitterShouldTrail": true, - "id": "{4e72df75-67d7-4ce2-bdea-e78adae8904a}", - "lastEdited": 1535751362199482, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "lifespan": 10, - "maxParticles": 40, - "name": "Rays", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "particleRadius": 0.75, - "polarFinish": 3.1415927410125732, - "position": { - "blue": 3.814434051513672, - "green": 1.44122314453125, - "red": 1.2319090366363525, - "x": -2.7680909633636475, - "y": -8.05877685546875, - "z": -0.18556594848632812 - }, - "queryAACube": { - "scale": 4.330127239227295, - "x": -0.9331545829772949, - "y": -0.7238404750823975, - "z": 1.6493704319000244 - }, - "radiusFinish": 0.10000000149011612, - "radiusStart": 0, - "rotation": { - "w": 0.9594720602035522, - "x": -1.52587890625e-05, - "y": 0.28178834915161133, - "z": -4.57763671875e-05 - }, - "script": "file:///C:/Users/wayne/development/raysTryAgainEntityScript.js", - "speedSpread": 0, - "spinFinish": null, - "spinStart": null, - "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", - "type": "ParticleEffect", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, { "alpha": 0, "alphaFinish": 0, @@ -988,7 +485,7 @@ "y": 227, "z": 211 }, - "created": "2018-08-31T21:35:56Z", + "created": "2018-09-04T20:35:23Z", "dimensions": { "blue": 2.5, "green": 2.5, @@ -1021,10 +518,11 @@ }, "emitRate": 2, "emitSpeed": 0, - "id": "{3482eecd-0acc-4fb9-bd67-b160fa7c14d7}", - "lastEdited": 1535751362200185, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "id": "{4b942dd2-dcfa-4959-8758-073c56d6a380}", + "lastEdited": 1536093329670643, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", "lifespan": 10, + "locked": true, "maxParticles": 40, "name": "Rays", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", @@ -1034,9 +532,9 @@ "blue": 1.3553659915924072, "green": 1.44122314453125, "red": 2.572803497314453, - "x": -1.4271965026855469, - "y": -8.05877685546875, - "z": -2.6446340084075928 + "x": 2.572803497314453, + "y": 1.44122314453125, + "z": 1.3553659915924072 }, "queryAACube": { "scale": 4.330127239227295, @@ -1052,7 +550,6 @@ "y": 0.19707024097442627, "z": -7.62939453125e-05 }, - "script": "file:///C:/Users/wayne/development/raysBackEntityScript.js", "speedSpread": 0, "spinFinish": null, "spinStart": null, @@ -1060,46 +557,6 @@ "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, - { - "clientOnly": false, - "created": "2018-08-31T21:35:56Z", - "dimensions": { - "blue": 0.06014331430196762, - "green": 2.582186460494995, - "red": 2.582186698913574, - "x": 2.582186698913574, - "y": 2.582186460494995, - "z": 0.06014331430196762 - }, - "id": "{729e9f39-5750-42a4-97a8-0fc79b300a32}", - "lastEdited": 1535751362197365, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", - "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/oopsDialog.fbx", - "name": "Oops Dialog", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 1.45927095413208, - "green": 1.6763916015625, - "red": 0, - "x": -4, - "y": -7.8236083984375, - "z": -2.54072904586792 - }, - "queryAACube": { - "scale": 3.6522583961486816, - "x": -1.8261291980743408, - "y": -0.14973759651184082, - "z": -0.36685824394226074 - }, - "rotation": { - "w": 0.8684672117233276, - "x": -4.57763671875e-05, - "y": 0.4957197904586792, - "z": -7.62939453125e-05 - }, - "type": "Model", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, { "angularDamping": 0, "clientOnly": false, @@ -1108,7 +565,7 @@ "green": 0, "red": 255 }, - "created": "2018-08-31T21:35:56Z", + "created": "2018-09-04T20:35:23Z", "dimensions": { "blue": 0.20000000298023224, "green": 0.20000000298023224, @@ -1117,18 +574,163 @@ "y": 0.20000000298023224, "z": 0.20000000298023224 }, - "id": "{bd032130-bbd7-4c0b-9b98-ab9c9362be9b}", - "lastEdited": 1535751362198499, - "lastEditedBy": "{8cc0d68c-b354-4c5f-992d-56b89e8072c5}", + "id": "{3b13d766-763a-4d44-99d8-42c7b9d7139e}", + "lastEdited": 1536093329671361, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.8216662406921387, + "green": 0, + "red": 1.2409718036651611, + "x": 1.2409718036651611, + "y": 0, + "z": 3.8216662406921387 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 1.0677666664123535, + "y": -0.17320507764816284, + "z": 3.648461103439331 + }, + "rotation": { + "w": 0.6444342136383057, + "x": -0.08220034837722778, + "y": -0.6649118661880493, + "z": 0.3684900999069214 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 0.25, + "clientOnly": false, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255, + "x": 255, + "y": 255, + "z": 255 + }, + "created": "2018-09-04T20:35:23Z", + "dimensions": { + "blue": 13.24000072479248, + "green": 13.24000072479248, + "red": 13.24000072479248, + "x": 13.24000072479248, + "y": 13.24000072479248, + "z": 13.24000072479248 + }, + "emitAcceleration": { + "blue": 0, + "green": 0.10000000149011612, + "red": 0, + "x": 0, + "y": 0.10000000149011612, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "emitRate": 6, + "emitSpeed": 0, + "emitterShouldTrail": true, + "id": "{e36388d2-7209-4f26-974b-54d314e106a5}", + "lastEdited": 1536093329671654, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 10, + "name": "Stars", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.07000000029802322, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 3.78922963142395, + "green": 0.5220947265625, + "red": 1.1928560733795166, + "x": 1.1928560733795166, + "y": 0.5220947265625, + "z": 3.78922963142395 + }, + "queryAACube": { + "scale": 22.932353973388672, + "x": -10.273321151733398, + "y": -10.944082260131836, + "z": -7.676947593688965 + }, + "radiusFinish": 0, + "radiusStart": 0, + "rotation": { + "w": 0.996429443359375, + "x": -1.52587890625e-05, + "y": -0.08442819118499756, + "z": -4.57763671875e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-09-04T20:35:23Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{1dbde8e2-c715-4c09-814e-e411803278c9}", + "lastEdited": 1536093329670778, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, "name": "Particle Rotate", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { "blue": 3.8216662406921387, "green": 0.0772705078125, "red": 1.2409718036651611, - "x": -2.759028196334839, - "y": -9.4227294921875, - "z": -0.17833375930786133 + "x": 1.2409718036651611, + "y": 0.0772705078125, + "z": 3.8216662406921387 }, "queryAACube": { "scale": 0.3464101552963257, @@ -1148,8 +750,479 @@ "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-04T20:35:23Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{a06f47da-06e5-4bcd-baa2-37816130f2bd}", + "lastEdited": 1536093329671244, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 6.1806135177612305, + "green": 1.1588134765625, + "red": 1.4755167961120605, + "x": 1.4755167961120605, + "y": 1.1588134765625, + "z": 6.1806135177612305 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -4.365227222442627, + "y": -4.6819305419921875, + "z": 0.33986949920654297 + }, + "rotation": { + "w": 0.8637980222702026, + "x": -4.57763671875e-05, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 1, + "clientOnly": false, + "color": { + "blue": 255, + "green": 205, + "red": 3 + }, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 204, + "red": 0, + "x": 0, + "y": 204, + "z": 255 + }, + "created": "2018-09-04T20:35:23Z", + "dimensions": { + "blue": 2.5, + "green": 2.5, + "red": 2.5, + "x": 2.5, + "y": 2.5, + "z": 2.5 + }, + "emitAcceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 0.9993909597396851, + "x": 0.034897372126579285, + "y": -1.525880907138344e-05, + "z": -1.525880907138344e-05 + }, + "emitRate": 2, + "emitSpeed": 0, + "emitterShouldTrail": true, + "id": "{0b1d2de0-39bf-4416-ac72-aae7ddbb3ff0}", + "lastEdited": 1536093329672220, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 40, + "name": "Rays", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.75, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 3.814434051513672, + "green": 1.44122314453125, + "red": 1.2319090366363525, + "x": 1.2319090366363525, + "y": 1.44122314453125, + "z": 3.814434051513672 + }, + "queryAACube": { + "scale": 4.330127239227295, + "x": -0.9331545829772949, + "y": -0.7238404750823975, + "z": 1.6493704319000244 + }, + "radiusFinish": 0.10000000149011612, + "radiusStart": 0, + "rotation": { + "w": 0.9594720602035522, + "x": -1.52587890625e-05, + "y": 0.28178834915161133, + "z": -4.57763671875e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-09-04T20:35:23Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{01d9478b-50ae-4f09-b67a-e62a4d133cdf}", + "lastEdited": 1536093329672981, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.8216662406921387, + "green": 0.15618896484375, + "red": 1.2409718036651611, + "x": 1.2409718036651611, + "y": 0.15618896484375, + "z": 3.8216662406921387 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 1.0677666664123535, + "y": -0.017016112804412842, + "z": 3.648461103439331 + }, + "rotation": { + "w": 0.6747081279754639, + "x": -0.06532388925552368, + "y": -0.6342412233352661, + "z": 0.37175559997558594 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-04T20:35:23Z", + "dimensions": { + "blue": 6.9401350021362305, + "green": 0.04553089290857315, + "red": 7.004304885864258, + "x": 7.004304885864258, + "y": 0.04553089290857315, + "z": 6.9401350021362305 + }, + "id": "{718b3532-0c3c-4689-ac41-dca8fedffc4a}", + "lastEdited": 1536093496994604, + "lastEditedBy": "{4eecd88f-ef9b-4a83-bb9a-7f7496209c6b}", + "locked": true, + "name": "floor", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.6175529956817627, + "green": 0.15221074223518372, + "red": 4.108861923217773, + "x": 4.108861923217773, + "y": 0.15221074223518372, + "z": 3.6175529956817627 + }, + "queryAACube": { + "scale": 9.860417366027832, + "x": -0.8213467597961426, + "y": -4.777997970581055, + "z": -1.3126556873321533 + }, + "rotation": { + "w": 0.8660253882408142, + "x": -1.5922749298624694e-05, + "y": 0.5, + "z": -4.572480611386709e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-04T20:35:23Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{44442e52-e0cf-45d3-9916-678b8aa56a22}", + "lastEdited": 1536093329672691, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 5.268576622009277, + "green": 1.1588134765625, + "red": 6.100250244140625, + "x": 6.100250244140625, + "y": 1.1588134765625, + "z": 5.268576622009277 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": 0.2595062255859375, + "y": -4.6819305419921875, + "z": -0.5721673965454102 + }, + "rotation": { + "w": 0.9662165641784668, + "x": -4.57763671875e-05, + "y": -0.2576791048049927, + "z": 1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-09-04T20:35:23Z", + "dimensions": { + "blue": 14.40000057220459, + "green": 14.40000057220459, + "red": 14.40000057220459, + "x": 14.40000057220459, + "y": 14.40000057220459, + "z": 14.40000057220459 + }, + "id": "{8a045310-b82f-4021-9963-0b98d317995c}", + "lastEdited": 1536093329670891, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 2.3440732955932617, + "green": 1.7684326171875, + "red": 1.8812973499298096, + "x": 1.8812973499298096, + "y": 1.7684326171875, + "z": 2.3440732955932617 + }, + "queryAACube": { + "scale": 24.9415340423584, + "x": -10.589469909667969, + "y": -10.7023344039917, + "z": -10.126693725585938 + }, + "rotation": { + "w": 0.8697794675827026, + "x": -1.52587890625e-05, + "y": 0.4933699369430542, + "z": -4.57763671875e-05 + }, + "shapeType": "box", + "skyboxMode": "enabled", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 0.25, + "clientOnly": false, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255, + "x": 255, + "y": 255, + "z": 255 + }, + "created": "2018-09-04T20:35:23Z", + "dimensions": { + "blue": 13.24000072479248, + "green": 13.24000072479248, + "red": 13.24000072479248, + "x": 13.24000072479248, + "y": 13.24000072479248, + "z": 13.24000072479248 + }, + "emitAcceleration": { + "blue": 0, + "green": 0.10000000149011612, + "red": 0, + "x": 0, + "y": 0.10000000149011612, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "emitRate": 6, + "emitSpeed": 0, + "id": "{b310070f-6af8-4c1d-9ad7-ea1070b53763}", + "lastEdited": 1536093329670211, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 10, + "name": "Stars", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.07000000029802322, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 1.3712034225463867, + "green": 0.5220947265625, + "red": 2.6281180381774902, + "x": 2.6281180381774902, + "y": 0.5220947265625, + "z": 1.3712034225463867 + }, + "queryAACube": { + "scale": 22.932353973388672, + "x": -8.838058471679688, + "y": -10.944082260131836, + "z": -10.09497356414795 + }, + "radiusFinish": 0, + "radiusStart": 0, + "rotation": { + "w": 0.9852597713470459, + "x": -1.52587890625e-05, + "y": -0.17106890678405762, + "z": -7.62939453125e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "angularDamping": 0, + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-09-04T20:35:23Z", + "dimensions": { + "blue": 0.20000000298023224, + "green": 0.20000000298023224, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 0.20000000298023224, + "z": 0.20000000298023224 + }, + "id": "{7c24a30b-47d3-4cf6-af92-5a7959a3cb5d}", + "lastEdited": 1536093329671011, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "name": "Particle Rotate", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.3116319179534912, + "green": 0, + "red": 2.705613613128662, + "x": 2.705613613128662, + "y": 0, + "z": 1.3116319179534912 + }, + "queryAACube": { + "scale": 0.3464101552963257, + "x": 2.5324084758758545, + "y": -0.17320507764816284, + "z": 1.1384267807006836 + }, + "rotation": { + "w": 0.9127794504165649, + "x": 0.2575265169143677, + "y": 0.15553522109985352, + "z": 0.2761729955673218 + }, + "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", + "scriptTimestamp": 1532724769253, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false } ], - "Id": "{99bc5288-05fd-4d80-a406-a44c948f7066}", + "Id": "{c9a2e576-13f3-44fa-8d41-a466b33d16c6}", "Version": 93 -} \ No newline at end of file +} diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 13d500b909..0778e2a44b 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -624,7 +624,7 @@ Controller.keyReleaseEvent.connect(keyReleaseEvent); Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); - //Window.domainConnectionRefused.connect(onDomainConnectionRefused); + Window.domainConnectionRefused.connect(onDomainConnectionRefused); Window.stillSnapshotTaken.connect(onSnapshotTaken); Window.snapshot360Taken.connect(onSnapshotTaken); Window.processingGifStarted.connect(processingGif); From 7af69b54501f5989de5bab6d606d69021a9bdf56 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 4 Sep 2018 13:53:25 -0700 Subject: [PATCH 229/744] Check that a requested listened port is used in NodeList --- libraries/networking/src/LimitedNodeList.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index b6b2369703..db6ed15792 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -56,13 +56,20 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : qRegisterMetaType("ConnectionStep"); auto port = (socketListenPort != INVALID_PORT) ? socketListenPort : LIMITED_NODELIST_LOCAL_PORT.get(); _nodeSocket.bind(QHostAddress::AnyIPv4, port); - qCDebug(networking) << "NodeList socket is listening on" << _nodeSocket.localPort(); + quint16 assignedPort = _nodeSocket.localPort(); + if (socketListenPort != INVALID_PORT && socketListenPort != 0 && socketListenPort != assignedPort) { + qCCritical(networking) << "NodeList is unable to assign requested port of" << socketListenPort; + } + qCDebug(networking) << "NodeList socket is listening on" << assignedPort; if (dtlsListenPort != INVALID_PORT) { // only create the DTLS socket during constructor if a custom port is passed _dtlsSocket = new QUdpSocket(this); _dtlsSocket->bind(QHostAddress::AnyIPv4, dtlsListenPort); + if (dtlsListenPort != 0 && _dtlsSocket->localPort() != dtlsListenPort) { + qCDebug(networking) << "NodeList is unable to assign requested DTLS port of" << dtlsListenPort; + } qCDebug(networking) << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort(); } From 002c6547ee182eb32b24e366a5c2811dd6bd9fb5 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 4 Sep 2018 14:08:13 -0700 Subject: [PATCH 230/744] entity script URL update --- interface/resources/serverless/redirect.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index 72c4abc16b..0f58574c1e 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -44,7 +44,7 @@ "y": -0.22495104372501373, "z": 0 }, - "script": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/scripts/redirect/zoneTryAgainEntityScript.js", + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/zoneTryAgainEntityScript.js", "shapeType": "box", "type": "Zone", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" @@ -88,7 +88,7 @@ "y": -0.36650121212005615, "z": 0 }, - "script": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/scripts/redirect/zoneBackEntityScript.js", + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/zoneBackEntityScript.js", "shapeType": "box", "type": "Zone", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" @@ -364,7 +364,7 @@ "y": 0.4179598093032837, "z": -0.0001068115234375 }, - "script": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/scripts/redirect/backEntityScript.js", + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/backEntityScript.js", "scriptTimestamp": 1535751754379, "shapeType": "static-mesh", "type": "Model", @@ -454,7 +454,7 @@ "y": 0.5693598985671997, "z": -0.0001068115234375 }, - "script": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/scripts/redirect/tryAgainEntityScript.js", + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/tryAgainEntityScript.js", "shapeType": "static-mesh", "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" From e210fadc7cb599b9f6a72b5dade5398678ca51f7 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 4 Sep 2018 14:36:56 -0700 Subject: [PATCH 231/744] Fix for glitch when entering inAir state from takeoff. This was due to a frame lag of blend factor used for inAir blending. So the first frame the upward velocity would be 0, followed by 3.5 m/s the next frame. This is fixed by using the workingVelocity instead of _lastVelocity to drive the blend. --- libraries/animation/src/Rig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index c3e679096b..13cf165dac 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -926,7 +926,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos // compute blend based on velocity const float JUMP_SPEED = 3.5f; - float alpha = glm::clamp(-_lastVelocity.y / JUMP_SPEED, -1.0f, 1.0f) + 1.0f; + float alpha = glm::clamp(-workingVelocity.y / JUMP_SPEED, -1.0f, 1.0f) + 1.0f; _animVars.set("inAirAlpha", alpha); } From 9e76c164ac76fcc6dfadb05b79da96a862188d97 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 4 Sep 2018 16:25:47 -0700 Subject: [PATCH 232/744] fix aabox operator+= --- libraries/shared/src/AABox.h | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index e0bb1343f8..f41bb8a814 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -86,19 +86,12 @@ public: AABox clamp(float min, float max) const; inline AABox& operator+=(const glm::vec3& point) { - // Branchless version of: - //if (isInvalid()) { - // _corner = glm::min(_corner, point); - //} else { - // glm::vec3 maximum(_corner + _scale); - // _corner = glm::min(_corner, point); - // maximum = glm::max(maximum, point); - // _scale = maximum - _corner; - //} - float blend = (float)isInvalid(); - glm::vec3 maximumScale(glm::max(_scale, point - _corner)); + bool valid = !isInvalid(); + glm::vec3 maximum = glm::max(_corner + _scale, point); _corner = glm::min(_corner, point); - _scale = blend * _scale + (1.0f - blend) * maximumScale; + if (valid) { + _scale = maximum - _corner; + } return (*this); } @@ -136,7 +129,7 @@ public: static const glm::vec3 INFINITY_VECTOR; - bool isInvalid() const { return _corner == INFINITY_VECTOR; } + bool isInvalid() const { return _corner.x == std::numeric_limits::infinity(); } void clear() { _corner = INFINITY_VECTOR; _scale = glm::vec3(0.0f); } From 99e6e3d1128826f86ec529743032e8da6e2820ce Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 4 Sep 2018 17:18:26 -0700 Subject: [PATCH 233/744] Collision Pick added to teleport.js --- .../controllers/controllerModules/teleport.js | 77 +++++++++++++++---- 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index e4dd1c43fa..c69382a47a 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -22,6 +22,7 @@ Script.include("/~/system/libraries/controllers.js"); (function() { // BEGIN LOCAL_SCOPE var TARGET_MODEL_URL = Script.resolvePath("../../assets/models/teleport-destination.fbx"); + var CANCEL_MODEL_URL = Script.resolvePath("../../assets/models/teleport-cancel.fbx"); var SEAT_MODEL_URL = Script.resolvePath("../../assets/models/teleport-seat.fbx"); var TARGET_MODEL_DIMENSIONS = { @@ -72,6 +73,12 @@ Script.include("/~/system/libraries/controllers.js"); alpha: 1, width: 0.025 }; + var cancelEnd = { + type: "model", + url: CANCEL_MODEL_URL, + dimensions: TARGET_MODEL_DIMENSIONS, + ignorePickIntersection: true + }; var teleportEnd = { type: "model", url: TARGET_MODEL_URL, @@ -84,18 +91,24 @@ Script.include("/~/system/libraries/controllers.js"); dimensions: TARGET_MODEL_DIMENSIONS, ignorePickIntersection: true }; - - - var teleportRenderStates = [{name: "cancel", path: cancelPath}, + + var collisionEnd = { + type: "sphere", + dimensions: {x: 0, y: 0, z: 0}, + ignorePickIntersection: true + }; + + var teleportRenderStates = [{name: "noend", path: cancelPath}, + {name: "cancel", path: cancelPath, end: cancelEnd}, {name: "teleport", path: teleportPath, end: teleportEnd}, - {name: "seat", path: seatPath, end: seatEnd}]; + {name: "seat", path: seatPath, end: seatEnd}, + {name: "invisible", end: collisionEnd}]; var DEFAULT_DISTANCE = 8.0; - var teleportDefaultRenderStates = [{name: "cancel", distance: DEFAULT_DISTANCE, path: cancelPath}]; + var teleportDefaultRenderStates = [{name: "noend", distance: DEFAULT_DISTANCE, path: cancelPath}]; var ignoredEntities = []; - var TELEPORTER_STATES = { IDLE: 'idle', TARGETTING: 'targetting', @@ -106,6 +119,7 @@ Script.include("/~/system/libraries/controllers.js"); NONE: 'none', // Not currently targetting anything INVISIBLE: 'invisible', // The current target is an invvsible surface INVALID: 'invalid', // The current target is invalid (wall, ceiling, etc.) + CANCEL: 'cancel', // Insufficient space to accommodate the avatar capsule SURFACE: 'surface', // The current target is a valid surface SEAT: 'seat' // The current target is a seat }; @@ -122,6 +136,9 @@ Script.include("/~/system/libraries/controllers.js"); this.state = TELEPORTER_STATES.IDLE; this.currentTarget = TARGET.INVALID; this.currentResult = null; + this.capsuleHeight = 2.0; + this.capsuleRadius = 0.25; + this.pickHeightOffset = 0.01; this.getOtherModule = function() { var otherModule = this.hand === RIGHT_HAND ? leftTeleporter : rightTeleporter; @@ -143,6 +160,7 @@ Script.include("/~/system/libraries/controllers.js"); defaultRenderStates: teleportDefaultRenderStates, maxDistance: 8.0 }); + this.teleportParabolaHandInvisible = Pointers.createPointer(PickType.Parabola, { joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", dirOffset: { x: 0, y: 1, z: 0.1 }, @@ -157,6 +175,19 @@ Script.include("/~/system/libraries/controllers.js"); renderStates: teleportRenderStates, maxDistance: 8.0 }); + + this.teleportCollisionPick = Picks.createPick(PickType.Collision, { + enabled: true, + filter: Picks.PICK_ENTITIES + Picks.PICK_AVATARS, + shape: { + shapeType: "capsule-y", + dimensions: { x: _this.capsuleRadius * 2.0, y: _this.capsuleHeight - (_this.capsuleRadius * 2.0), z: _this.capsuleRadius * 2.0 } + }, + position: { x: 0, y: _this.pickHeightOffset + (_this.capsuleHeight * 0.5), z: 0 }, + parentID: Pointers.getPointerProperties(_this.teleportParabolaHandInvisible).renderStates["invisible"].end, + threshold: 0.05 + }); + this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Parabola, { joint: "Avatar", filter: Picks.PICK_ENTITIES, @@ -170,6 +201,7 @@ Script.include("/~/system/libraries/controllers.js"); defaultRenderStates: teleportDefaultRenderStates, maxDistance: 8.0 }); + this.teleportParabolaHeadInvisible = Pointers.createPointer(PickType.Parabola, { joint: "Avatar", filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, @@ -184,11 +216,14 @@ Script.include("/~/system/libraries/controllers.js"); }); this.cleanup = function() { - Pointers.removePointer(this.teleportParabolaHandVisible); - Pointers.removePointer(this.teleportParabolaHandInvisible); - Pointers.removePointer(this.teleportParabolaHeadVisible); - Pointers.removePointer(this.teleportParabolaHeadInvisible); + Pointers.removePointer(_this.teleportParabolaHandVisible); + Pointers.removePointer(_this.teleportParabolaHandInvisible); + Pointers.removePointer(_this.teleportParabolaHeadVisible); + Pointers.removePointer(_this.teleportParabolaHeadInvisible); + Picks.removePick(_this.teleportCollisionPick); }; + + // Picks.setIgnoreItems(_this.teleportCollisionPick, []); this.axisButtonStateX = 0; // Left/right axis button pressed. this.axisButtonStateY = 0; // Up/down axis button pressed. @@ -280,8 +315,9 @@ Script.include("/~/system/libraries/controllers.js"); } else { result = Pointers.getPrevPickResult(_this.teleportParabolaHandInvisible); } - - var teleportLocationType = getTeleportTargetType(result); + var collisionResult = Picks.getPrevPickResult(_this.teleportCollisionPick); + + var teleportLocationType = getTeleportTargetType(result, collisionResult); if (teleportLocationType === TARGET.INVISIBLE) { if (mode === 'head') { result = Pointers.getPrevPickResult(_this.teleportParabolaHeadVisible); @@ -293,11 +329,13 @@ Script.include("/~/system/libraries/controllers.js"); if (teleportLocationType === TARGET.NONE) { // Use the cancel default state - this.setTeleportState(mode, "cancel", ""); + this.setTeleportState(mode, "noend", ""); } else if (teleportLocationType === TARGET.INVALID || teleportLocationType === TARGET.INVISIBLE) { - this.setTeleportState(mode, "", "cancel"); + this.setTeleportState(mode, "", "noend"); + } else if (teleportLocationType === TARGET.CANCEL) { + this.setTeleportState(mode, "cancel", "invisible"); } else if (teleportLocationType === TARGET.SURFACE) { - this.setTeleportState(mode, "teleport", ""); + this.setTeleportState(mode, "teleport", "invisible"); } else if (teleportLocationType === TARGET.SEAT) { this.setTeleportState(mode, "", "seat"); } @@ -355,6 +393,7 @@ Script.include("/~/system/libraries/controllers.js"); // related to repositioning the avatar after you teleport var FOOT_JOINT_NAMES = ["RightToe_End", "RightToeBase", "RightFoot"]; var DEFAULT_ROOT_TO_FOOT_OFFSET = 0.5; + function getAvatarFootOffset() { // find a valid foot jointIndex @@ -395,7 +434,8 @@ Script.include("/~/system/libraries/controllers.js"); // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from your avatar's up, then // you can't teleport there. var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; - function getTeleportTargetType(result) { + + function getTeleportTargetType(result, collisionResult) { if (result.type === Picks.INTERSECTED_NONE) { return TARGET.NONE; } @@ -421,6 +461,11 @@ Script.include("/~/system/libraries/controllers.js"); if (angle > MAX_ANGLE_FROM_UP_TO_TELEPORT) { return TARGET.INVALID; } else { + if (collisionResult.collisionRegion != undefined) { + if (collisionResult.intersects) { + return TARGET.CANCEL; + } + } return TARGET.SURFACE; } } From adcabf52c5f2945ce368a39b194190a86f698902 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Tue, 4 Sep 2018 17:33:18 -0700 Subject: [PATCH 234/744] Refactoring the FBXReader_MEsh.cpp for better interleaving --- libraries/fbx/src/FBXReader_Mesh.cpp | 226 +++++++++++++-------------- 1 file changed, 110 insertions(+), 116 deletions(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index e6d9321fb5..4ecca2b234 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -585,16 +585,9 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { FBXMesh& fbxMesh = extractedMesh; graphics::MeshPointer mesh(new graphics::Mesh()); - // bool blendShapes = !fbxMesh.blendshapes.empty(); + bool hasBlendShapes = !fbxMesh.blendshapes.empty(); int numVerts = extractedMesh.vertices.size(); - // Grab the vertices in a buffer - auto vb = std::make_shared(); - vb->setData(extractedMesh.vertices.size() * sizeof(glm::vec3), - (const gpu::Byte*) extractedMesh.vertices.data()); - gpu::BufferView vbv(vb, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - // mesh->setVertexBuffer(vbv); - if (!fbxMesh.normals.empty() && fbxMesh.tangents.empty()) { // Fill with a dummy value to force tangents to be present if there are normals fbxMesh.tangents.reserve(fbxMesh.normals.size()); @@ -609,50 +602,61 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { } } - // evaluate all attribute channels sizes - const int normalsSize = fbxMesh.normals.size() * sizeof(NormalType); - const int tangentsSize = fbxMesh.tangents.size() * sizeof(NormalType); + // evaluate all attribute elements and data sizes + + // Position is a vec3 + const auto positionElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + const int positionsSize = numVerts * positionElement.getSize(); + + // Normal and tangent are always there together packed in normalized xyz32bits word (times 2) + const auto normalElement = FBX_NORMAL_ELEMENT; + const int normalsSize = fbxMesh.normals.size() * normalElement.getSize(); + const int tangentsSize = fbxMesh.tangents.size() * normalElement.getSize(); // If there are normals then there should be tangents - assert(normalsSize <= tangentsSize); if (tangentsSize > normalsSize) { qWarning() << "Unexpected tangents in " << url; } const auto normalsAndTangentsSize = normalsSize + tangentsSize; - // const int normalsAndTangentsStride = 2 * sizeof(NormalType); - const int colorsSize = fbxMesh.colors.size() * sizeof(ColorType); + const int normalsAndTangentsStride = 2 * normalElement.getSize(); + + // Color attrib + const auto colorElement = FBX_COLOR_ELEMENT; + const int colorsSize = fbxMesh.colors.size() * colorElement.getSize(); + // Texture coordinates are stored in 2 half floats - const int texCoordsSize = fbxMesh.texCoords.size() * sizeof(vec2h); - const int texCoords1Size = fbxMesh.texCoords1.size() * sizeof(vec2h); + const auto texCoordsElement = gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV); + const int texCoordsSize = fbxMesh.texCoords.size() * texCoordsElement.getSize(); + const int texCoords1Size = fbxMesh.texCoords1.size() * texCoordsElement.getSize(); - int clusterIndicesSize = fbxMesh.clusterIndices.size() * sizeof(uint8_t); - if (fbxMesh.clusters.size() > UINT8_MAX) { - // we need 16 bits instead of just 8 for clusterIndices - clusterIndicesSize *= 2; - } + // Support for 4 skinning clusters: + // 4 Indices are uint8 ideally, uint16 if more than 256. + const auto clusterIndiceElement = (fbxMesh.clusters.size() < UINT8_MAX ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW)); + // 4 Weights are normalized 16bits + const auto clusterWeightElement = gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW); - const int clusterWeightsSize = fbxMesh.clusterWeights.size() * sizeof(uint16_t); + // Cluster indices and weights must be the same sizes + const int NUM_CLUSTERS_PER_VERT = 4; + const int numVertClusters = (fbxMesh.clusterIndices.size() == fbxMesh.clusterWeights.size() ? fbxMesh.clusterIndices.size() / NUM_CLUSTERS_PER_VERT : 0); + const int clusterIndicesSize = numVertClusters * clusterIndiceElement.getSize(); + const int clusterWeightsSize = numVertClusters * clusterWeightElement.getSize(); - // Normals and tangents are interleaved - const int normalsOffset = 0; - // const int tangentsOffset = normalsOffset + sizeof(NormalType); - const int totalNTSize = normalsOffset + normalsSize + tangentsSize; - //const int colorsOffset = normalsOffset + normalsSize + tangentsSize; - - const int colorsOffset = 0; + // Decide on where to put what seequencially in a big buffer: + const int positionsOffset = 0; + const int normalsAndTangentsOffset = positionsOffset + positionsSize; + const int colorsOffset = normalsAndTangentsOffset + normalsAndTangentsSize; const int texCoordsOffset = colorsOffset + colorsSize; const int texCoords1Offset = texCoordsOffset + texCoordsSize; const int clusterIndicesOffset = texCoords1Offset + texCoords1Size; const int clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; - const int totalAttributeSize = clusterWeightsOffset + clusterWeightsSize; + const int totalVertsSize = clusterWeightsOffset + clusterWeightsSize; - // Copy all attribute data in a single attribute buffer + // Copy all vertex data in a single buffer + auto vertBuffer = std::make_shared(); + vertBuffer->resize(totalVertsSize); - auto attribNTBuffer = std::make_shared(); - attribNTBuffer->resize(totalNTSize); - - auto attribBuffer = std::make_shared(); - attribBuffer->resize(totalAttributeSize); + // First positions + vertBuffer->setSubData(positionsOffset, positionsSize, (const gpu::Byte*) extractedMesh.vertices.data()); // Interleave normals and tangents if (normalsSize > 0) { @@ -660,8 +664,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { normalsAndTangents.reserve(fbxMesh.normals.size() + fbxMesh.tangents.size()); for (auto normalIt = fbxMesh.normals.constBegin(), tangentIt = fbxMesh.tangents.constBegin(); - normalIt != fbxMesh.normals.constEnd(); - ++normalIt, ++tangentIt) { + normalIt != fbxMesh.normals.constEnd(); + ++normalIt, ++tangentIt) { #if FBX_PACK_NORMALS const auto normal = normalizeDirForPacking(*normalIt); const auto tangent = normalizeDirForPacking(*tangentIt); @@ -674,10 +678,10 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { normalsAndTangents.push_back(packedNormal); normalsAndTangents.push_back(packedTangent); } - attribNTBuffer->setSubData(normalsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.data()); + vertBuffer->setSubData(normalsAndTangentsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.data()); } - + // Pack colors if (colorsSize > 0) { #if FBX_PACK_COLORS std::vector colors; @@ -686,12 +690,13 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { for (const auto& color : fbxMesh.colors) { colors.push_back(glm::packUnorm4x8(glm::vec4(color, 1.0f))); } - attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); + vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); #else - attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) fbxMesh.colors.constData()); + vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) fbxMesh.colors.constData()); #endif } + // Pack Texcoords 0 and 1 (if exists) if (texCoordsSize > 0) { QVector texCoordData; texCoordData.reserve(fbxMesh.texCoords.size()); @@ -702,9 +707,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); texCoordData.push_back(texCoordVec2h); } - attribBuffer->setSubData(texCoordsOffset, texCoordsSize, (const gpu::Byte*) texCoordData.constData()); + vertBuffer->setSubData(texCoordsOffset, texCoordsSize, (const gpu::Byte*) texCoordData.constData()); } - if (texCoords1Size > 0) { QVector texCoordData; texCoordData.reserve(fbxMesh.texCoords1.size()); @@ -715,100 +719,91 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); texCoordData.push_back(texCoordVec2h); } - attribBuffer->setSubData(texCoords1Offset, texCoords1Size, (const gpu::Byte*) texCoordData.constData()); + vertBuffer->setSubData(texCoords1Offset, texCoords1Size, (const gpu::Byte*) texCoordData.constData()); } - if (fbxMesh.clusters.size() < UINT8_MAX) { - // yay! we can fit the clusterIndices within 8-bits - int32_t numIndices = fbxMesh.clusterIndices.size(); - QVector clusterIndices; - clusterIndices.resize(numIndices); - for (int32_t i = 0; i < numIndices; ++i) { - assert(fbxMesh.clusterIndices[i] <= UINT8_MAX); - clusterIndices[i] = (uint8_t)(fbxMesh.clusterIndices[i]); + // Clusters data + if (clusterIndicesSize > 0) { + if (fbxMesh.clusters.size() < UINT8_MAX) { + // yay! we can fit the clusterIndices within 8-bits + int32_t numIndices = fbxMesh.clusterIndices.size(); + QVector clusterIndices; + clusterIndices.resize(numIndices); + for (int32_t i = 0; i < numIndices; ++i) { + assert(fbxMesh.clusterIndices[i] <= UINT8_MAX); + clusterIndices[i] = (uint8_t)(fbxMesh.clusterIndices[i]); + } + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); + } + else { + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) fbxMesh.clusterIndices.constData()); } - attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); - } else { - attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) fbxMesh.clusterIndices.constData()); } - attribBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) fbxMesh.clusterWeights.constData()); + if (clusterWeightsSize > 0) { + vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) fbxMesh.clusterWeights.constData()); + } + + // Now we decide on how to interleave the attributes and provide the vertices among bufers: + // Aka the Vertex format auto vf = std::make_shared(); - auto vbs = std::make_shared(); - - gpu::Offset buf0Offset = 12; - vf->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - vbs->addBuffer(vb, 0, buf0Offset); + gpu::Offset buf0Offset = 0; + vf->setAttribute(gpu::Stream::POSITION, 0, positionElement); + buf0Offset += positionElement.getSize(); gpu::Offset buf1Offset = 0; if (normalsSize) { - /* mesh->addAttribute(gpu::Stream::NORMAL, - graphics::BufferView(attribBuffer, normalsOffset, normalsAndTangentsSize, - normalsAndTangentsStride, FBX_NORMAL_ELEMENT)); - mesh->addAttribute(gpu::Stream::TANGENT, - graphics::BufferView(attribBuffer, tangentsOffset, normalsAndTangentsSize, - normalsAndTangentsStride, FBX_NORMAL_ELEMENT)); -*/ - vf->setAttribute(gpu::Stream::NORMAL, 1, FBX_NORMAL_ELEMENT, 0); - vf->setAttribute(gpu::Stream::TANGENT, 1, FBX_NORMAL_ELEMENT, 4); - buf1Offset = 8; - vbs->addBuffer(attribNTBuffer, 0, buf1Offset); + vf->setAttribute(gpu::Stream::NORMAL, 1, normalElement, buf1Offset); + buf1Offset = normalElement.getSize(); + vf->setAttribute(gpu::Stream::TANGENT, 1, normalElement, buf1Offset); + buf1Offset = normalElement.getSize(); } - gpu::Offset buf2Offset = 0; + gpu::Offset buf2Offset = (0); if (colorsSize) { - /* mesh->addAttribute(gpu::Stream::COLOR, - graphics::BufferView(attribBuffer, colorsOffset, colorsSize, FBX_COLOR_ELEMENT)); -*/ - vf->setAttribute(gpu::Stream::COLOR, 2, FBX_COLOR_ELEMENT, buf2Offset); - buf2Offset += 4; + vf->setAttribute(gpu::Stream::COLOR, 2, colorElement, buf2Offset); + buf2Offset += colorElement.getSize(); } if (texCoordsSize) { - /* mesh->addAttribute(gpu::Stream::TEXCOORD, - graphics::BufferView( attribBuffer, texCoordsOffset, texCoordsSize, - gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); - */ vf->setAttribute(gpu::Stream::TEXCOORD, 2, gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV), buf2Offset); - buf2Offset += 4; + vf->setAttribute(gpu::Stream::TEXCOORD, 2, texCoordsElement, buf2Offset); + buf2Offset += texCoordsElement.getSize(); } if (texCoords1Size) { - /* mesh->addAttribute( gpu::Stream::TEXCOORD1, - graphics::BufferView(attribBuffer, texCoords1Offset, texCoords1Size, - gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); - */ vf->setAttribute(gpu::Stream::TEXCOORD1, 2, gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV), buf2Offset); - buf2Offset += 4; - } else if (texCoordsSize) { - /* mesh->addAttribute(gpu::Stream::TEXCOORD1, - graphics::BufferView(attribBuffer, texCoordsOffset, texCoordsSize, - gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); - */ vf->setAttribute(gpu::Stream::TEXCOORD1, 2, gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV), buf2Offset - 4); + vf->setAttribute(gpu::Stream::TEXCOORD1, 2, texCoordsElement, buf2Offset); + buf2Offset += texCoordsElement.getSize(); + } + else if (texCoordsSize) { + vf->setAttribute(gpu::Stream::TEXCOORD1, 2, texCoordsElement, buf2Offset - texCoordsElement.getSize()); } - if (clusterIndicesSize) { - if (fbxMesh.clusters.size() < UINT8_MAX) { - /* mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, - graphics::BufferView(attribBuffer, clusterIndicesOffset, clusterIndicesSize, - gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW))); -*/ - vf->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, 2, gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW), buf2Offset); - buf2Offset += 4; - - } else { - /* mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, - graphics::BufferView(attribBuffer, clusterIndicesOffset, clusterIndicesSize, - gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW))); - */ vf->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, 2, gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW), buf2Offset); - buf2Offset += 8; - } + vf->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, 2, clusterIndiceElement, buf2Offset); + buf2Offset += clusterIndiceElement.getSize(); } if (clusterWeightsSize) { - /* mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, - graphics::BufferView(attribBuffer, clusterWeightsOffset, clusterWeightsSize, - gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW))); - */ vf->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, 2, gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW), buf2Offset); - buf2Offset += 8; + vf->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, 2, clusterWeightElement, buf2Offset); + buf2Offset += clusterWeightElement.getSize(); } + auto vbs = std::make_shared(); + + + auto vb = std::make_shared(); + vb->setData(extractedMesh.vertices.size() * sizeof(glm::vec3), + (const gpu::Byte*) extractedMesh.vertices.data()); + gpu::BufferView vbv(vb, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + vbs->addBuffer(vb, 0, buf0Offset); + + auto attribNTBuffer = std::make_shared(); + attribNTBuffer->resize(totalNTSize); + + auto attribBuffer = std::make_shared(); + attribBuffer->resize(totalAttributeSize); + + + { + vbs->addBuffer(attribNTBuffer, 0, buf1Offset); + auto vColorOffset = 0; auto vColorSize = colorsSize / numVerts; @@ -848,7 +843,6 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { vbs->addBuffer(attribBuffer, 0, vStride); } - mesh->setVertexFormatAndStream(vf, vbs); unsigned int totalIndices = 0; From 9e0fa08cf12af8009328dbe1a70506099b92043f Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 4 Sep 2018 17:46:23 -0700 Subject: [PATCH 235/744] adding property to location, updating domain --- interface/resources/serverless/redirect.json | 1317 +++++++----------- interface/src/Application.cpp | 2 + libraries/networking/src/AddressManager.cpp | 4 +- libraries/networking/src/AddressManager.h | 38 +- libraries/networking/src/DomainHandler.cpp | 1 + libraries/networking/src/DomainHandler.h | 2 + 6 files changed, 545 insertions(+), 819 deletions(-) diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index 0f58574c1e..f983a7ffef 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -9,7 +9,7 @@ "clientOnly": false, "collidesWith": "static,dynamic,kinematic,otherAvatar,", "collisionMask": 23, - "created": "2018-09-04T20:35:23Z", + "created": "2018-09-05T00:40:03Z", "dimensions": { "blue": 0.8660923838615417, "green": 1.921838402748108, @@ -18,25 +18,25 @@ "y": 1.921838402748108, "z": 0.8660923838615417 }, - "id": "{3889145e-9db2-46dc-9765-9ba7594457dc}", - "lastEdited": 1536093329670067, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "id": "{781e26c7-ecfa-44b2-b7ef-4e3278787fbc}", + "lastEdited": 1536108183362142, + "lastEditedBy": "{4656d4a8-5e61-4230-ab34-2888d7945bd6}", "locked": true, "name": "Try Again Zone", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { - "blue": 4.524896621704102, - "green": 1.9289360046386719, - "red": 2.893861770629883, - "x": 2.893861770629883, - "y": 1.9289360046386719, - "z": 4.524896621704102 + "blue": 4.225754737854004, + "green": 1.7677892446517944, + "red": 2.778148889541626, + "x": 2.778148889541626, + "y": 1.7677892446517944, + "z": 4.225754737854004 }, "queryAACube": { "scale": 2.4632973670959473, - "x": 1.6622130870819092, - "y": 0.6972873210906982, - "z": 3.293247938156128 + "x": 1.5465002059936523, + "y": 0.5361405611038208, + "z": 2.9941060543060303 }, "rotation": { "w": 0.9743700623512268, @@ -50,244 +50,13 @@ "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, { - "clientOnly": false, - "collidesWith": "static,dynamic,kinematic,otherAvatar,", - "collisionMask": 23, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 1.159199833869934, - "green": 2.8062009811401367, - "red": 1.6216505765914917, - "x": 1.6216505765914917, - "y": 2.8062009811401367, - "z": 1.159199833869934 - }, - "id": "{8facc8e1-bc17-4460-8a9c-868b07c1b387}", - "lastEdited": 1536093329669913, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", - "locked": true, - "name": "Back Zone", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 1.8632707595825195, - "green": 1.8022732734680176, - "red": 3.3276727199554443, - "x": 3.3276727199554443, - "y": 1.8022732734680176, - "z": 1.8632707595825195 - }, - "queryAACube": { - "scale": 3.4421300888061523, - "x": 1.6066076755523682, - "y": 0.0812082290649414, - "z": 0.14220571517944336 - }, - "rotation": { - "w": 0.9304176568984985, - "x": 0, - "y": -0.36650121212005615, - "z": 0 - }, - "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/zoneBackEntityScript.js", - "shapeType": "box", - "type": "Zone", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, - { - "angularDamping": 0, "clientOnly": false, "color": { "blue": 0, "green": 0, "red": 255 }, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{ed4142db-f3c9-441b-9091-cd1755b6aeb1}", - "lastEdited": 1536093329672493, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", - "locked": true, - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 1.3116319179534912, - "green": 0.15618896484375, - "red": 2.705613613128662, - "x": 2.705613613128662, - "y": 0.15618896484375, - "z": 1.3116319179534912 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 2.5324084758758545, - "y": -0.017016112804412842, - "z": 1.1384267807006836 - }, - "rotation": { - "w": 0.46953535079956055, - "x": -0.16719311475753784, - "y": -0.7982757091522217, - "z": 0.3380941152572632 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 0 - }, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 11.117486953735352, - "green": 3.580313205718994, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 3.580313205718994, - "z": 11.117486953735352 - }, - "id": "{d49f29ca-72b6-473d-917c-f7bf7d66f219}", - "lastEdited": 1536093329670344, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", - "locked": true, - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 0, - "green": 1.1583251953125, - "red": 4.971565246582031, - "x": 4.971565246582031, - "y": 1.1583251953125, - "z": 0 - }, - "queryAACube": { - "scale": 11.681488037109375, - "x": -0.8691787719726562, - "y": -4.6824188232421875, - "z": -5.8407440185546875 - }, - "rotation": { - "w": 0.8637980222702026, - "x": -4.57763671875e-05, - "y": 0.5038070678710938, - "z": -1.52587890625e-05 - }, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 0.06014331430196762, - "green": 2.582186460494995, - "red": 2.582186698913574, - "x": 2.582186698913574, - "y": 2.582186460494995, - "z": 0.06014331430196762 - }, - "id": "{6ccf519b-4e0b-4c23-a51b-305d0a64ab06}", - "lastEdited": 1536093329671943, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", - "locked": true, - "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/oopsDialog.fbx", - "name": "Oops Dialog", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 1.45927095413208, - "green": 1.6763916015625, - "red": 0, - "x": 0, - "y": 1.6763916015625, - "z": 1.45927095413208 - }, - "queryAACube": { - "scale": 3.6522583961486816, - "x": -1.8261291980743408, - "y": -0.14973759651184082, - "z": -0.36685824394226074 - }, - "rotation": { - "w": 0.8684672117233276, - "x": -4.57763671875e-05, - "y": 0.4957197904586792, - "z": -7.62939453125e-05 - }, - "type": "Model", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, - { - "angularDamping": 0, - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{625c95a4-1592-4996-bd48-7aa3fdbe6aee}", - "lastEdited": 1536093329671797, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", - "locked": true, - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 1.3116319179534912, - "green": 0.0772705078125, - "red": 2.705613613128662, - "x": 2.705613613128662, - "y": 0.0772705078125, - "z": 1.3116319179534912 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 2.5324084758758545, - "y": -0.09593456983566284, - "z": 1.1384267807006836 - }, - "rotation": { - "w": -0.38777750730514526, - "x": -0.37337303161621094, - "y": -0.8409399390220642, - "z": 0.055222392082214355 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-09-04T20:35:23Z", + "created": "2018-09-05T00:40:03Z", "dimensions": { "blue": 8.645400047302246, "green": 0.20000000298023224, @@ -296,23 +65,24 @@ "y": 0.20000000298023224, "z": 8.645400047302246 }, - "id": "{d356bb44-7888-476a-821e-961ae7d9330e}", - "lastEdited": 1536093329671479, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "id": "{e44fb546-b34a-4966-9b11-73556f800d21}", + "lastEdited": 1536107948776951, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", "locked": true, + "name": "ceiling", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { "blue": 4.846520900726318, - "green": 3.0651936531066895, - "red": 5.746071815490723, - "x": 5.746071815490723, - "y": 3.0651936531066895, + "green": 2.912982940673828, + "red": 5.739595890045166, + "x": 5.739595890045166, + "y": 2.912982940673828, "z": 4.846520900726318 }, "queryAACube": { "scale": 21.812576293945312, - "x": -5.160216331481934, - "y": -7.841094493865967, + "x": -5.16669225692749, + "y": -7.993305206298828, "z": -6.059767246246338 }, "rotation": { @@ -328,46 +98,48 @@ }, { "clientOnly": false, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 2.1097896099090576, - "green": 0.04847164824604988, - "red": 1.458284616470337, - "x": 1.458284616470337, - "y": 0.04847164824604988, - "z": 2.1097896099090576 + "color": { + "blue": 0, + "green": 0, + "red": 0 }, - "id": "{737f1b31-0342-4764-89fb-2ef7ddf83983}", - "lastEdited": 1536093329672351, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 6.9401350021362305, + "green": 0.04553089290857315, + "red": 7.004304885864258, + "x": 7.004304885864258, + "y": 0.04553089290857315, + "z": 6.9401350021362305 + }, + "id": "{8cd93fe5-16c0-44b7-b1e9-e7e06c4e9228}", + "lastEdited": 1536107948774796, + "lastEditedBy": "{4eecd88f-ef9b-4a83-bb9a-7f7496209c6b}", "locked": true, - "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal2.fbx", - "name": "Back", + "name": "floor", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { - "blue": 1.5835940837860107, - "green": 0.2467041015625, - "red": 3.0345542430877686, - "x": 3.0345542430877686, - "y": 0.2467041015625, - "z": 1.5835940837860107 + "blue": 3.6175529956817627, + "green": 0, + "red": 4.102385997772217, + "x": 4.102385997772217, + "y": 0, + "z": 3.6175529956817627 }, "queryAACube": { - "scale": 2.5651814937591553, - "x": 1.751963496208191, - "y": -1.0358866453170776, - "z": 0.3010033369064331 + "scale": 9.860417366027832, + "x": -0.8278226852416992, + "y": -4.930208683013916, + "z": -1.3126556873321533 }, "rotation": { - "w": 0.9084458351135254, - "x": -1.52587890625e-05, - "y": 0.4179598093032837, - "z": -0.0001068115234375 + "w": 0.8660253882408142, + "x": -1.5922749298624694e-05, + "y": 0.5, + "z": -4.572480611386709e-05 }, - "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/backEntityScript.js", - "scriptTimestamp": 1535751754379, - "shapeType": "static-mesh", - "type": "Model", + "shape": "Cube", + "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, { @@ -377,7 +149,7 @@ "green": 0, "red": 0 }, - "created": "2018-09-04T20:35:23Z", + "created": "2018-09-05T00:40:03Z", "dimensions": { "blue": 11.117486953735352, "green": 3.580313205718994, @@ -386,29 +158,77 @@ "y": 3.580313205718994, "z": 11.117486953735352 }, - "id": "{87f734c8-37bb-4ac0-b300-5028f7fe6f9d}", - "lastEdited": 1536093329670472, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "id": "{147272dc-a344-4171-9621-efc1c2095997}", + "lastEdited": 1536107948776823, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", "locked": true, + "name": "leftWall", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { - "blue": 2.662257671356201, - "green": 1.1585893630981445, - "red": 1.493349552154541, - "x": 1.493349552154541, - "y": 1.1585893630981445, - "z": 2.662257671356201 + "blue": 6.1806135177612305, + "green": 1.0066027641296387, + "red": 1.4690406322479248, + "x": 1.4690406322479248, + "y": 1.0066027641296387, + "z": 6.1806135177612305 }, "queryAACube": { "scale": 11.681488037109375, - "x": -4.3473944664001465, - "y": -4.682154655456543, - "z": -3.1784863471984863 + "x": -4.371703147888184, + "y": -4.834141254425049, + "z": 0.33986949920654297 }, "rotation": { - "w": 0.9666743278503418, + "w": 0.8637980222702026, "x": -4.57763671875e-05, - "y": -0.2560006380081177, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{5f2b89b8-47e3-4915-a966-d46307a40f06}", + "lastEdited": 1536107948774605, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "backWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 5.268576622009277, + "green": 1.0066027641296387, + "red": 6.093774318695068, + "x": 6.093774318695068, + "y": 1.0066027641296387, + "z": 5.268576622009277 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": 0.25303030014038086, + "y": -4.834141254425049, + "z": -0.5721673965454102 + }, + "rotation": { + "w": 0.9662165641784668, + "x": -4.57763671875e-05, + "y": -0.2576791048049927, "z": 1.52587890625e-05 }, "shape": "Cube", @@ -418,45 +238,43 @@ }, { "clientOnly": false, - "created": "2018-09-04T20:35:23Z", + "created": "2018-09-05T00:40:03Z", "dimensions": { - "blue": 2.1097896099090576, - "green": 0.04847164824604988, - "red": 1.458284616470337, - "x": 1.458284616470337, - "y": 0.04847164824604988, - "z": 2.1097896099090576 + "blue": 14.40000057220459, + "green": 14.40000057220459, + "red": 14.40000057220459, + "x": 14.40000057220459, + "y": 14.40000057220459, + "z": 14.40000057220459 }, - "id": "{9584bb57-b543-4d2b-af98-f3de618eef19}", - "lastEdited": 1536093329672070, + "id": "{baf96345-8f68-4068-af4c-3c690035852a}", + "lastEdited": 1536107948775591, "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", "locked": true, - "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal1.fbx", - "name": "Try Again", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { - "blue": 3.946338653564453, - "green": 0.2467041015625, - "red": 1.6013128757476807, - "x": 1.6013128757476807, - "y": 0.2467041015625, - "z": 3.946338653564453 + "blue": 2.3440732955932617, + "green": 1.6162219047546387, + "red": 1.8748211860656738, + "x": 1.8748211860656738, + "y": 1.6162219047546387, + "z": 2.3440732955932617 }, "queryAACube": { - "scale": 2.5651814937591553, - "x": 0.318722128868103, - "y": -1.0358866453170776, - "z": 2.663747787475586 + "scale": 24.9415340423584, + "x": -10.595945358276367, + "y": -10.854545593261719, + "z": -10.126693725585938 }, "rotation": { - "w": 0.8220492601394653, + "w": 0.8697794675827026, "x": -1.52587890625e-05, - "y": 0.5693598985671997, - "z": -0.0001068115234375 + "y": 0.4933699369430542, + "z": -4.57763671875e-05 }, - "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/tryAgainEntityScript.js", - "shapeType": "static-mesh", - "type": "Model", + "shapeType": "box", + "skyboxMode": "enabled", + "type": "Zone", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, { @@ -485,7 +303,7 @@ "y": 227, "z": 211 }, - "created": "2018-09-04T20:35:23Z", + "created": "2018-09-05T00:40:03Z", "dimensions": { "blue": 2.5, "green": 2.5, @@ -518,8 +336,8 @@ }, "emitRate": 2, "emitSpeed": 0, - "id": "{4b942dd2-dcfa-4959-8758-073c56d6a380}", - "lastEdited": 1536093329670643, + "id": "{639a51f0-8613-4e46-bc7e-fef24597df73}", + "lastEdited": 1536107948776693, "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", "lifespan": 10, "locked": true, @@ -530,16 +348,16 @@ "polarFinish": 3.1415927410125732, "position": { "blue": 1.3553659915924072, - "green": 1.44122314453125, - "red": 2.572803497314453, - "x": 2.572803497314453, - "y": 1.44122314453125, + "green": 1.2890124320983887, + "red": 2.5663273334503174, + "x": 2.5663273334503174, + "y": 1.2890124320983887, "z": 1.3553659915924072 }, "queryAACube": { "scale": 4.330127239227295, - "x": 0.40773987770080566, - "y": -0.7238404750823975, + "x": 0.4012637138366699, + "y": -0.8760511875152588, "z": -0.8096976280212402 }, "radiusFinish": 0.10000000149011612, @@ -557,246 +375,6 @@ "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, - { - "angularDamping": 0, - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{3b13d766-763a-4d44-99d8-42c7b9d7139e}", - "lastEdited": 1536093329671361, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", - "locked": true, - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 3.8216662406921387, - "green": 0, - "red": 1.2409718036651611, - "x": 1.2409718036651611, - "y": 0, - "z": 3.8216662406921387 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 1.0677666664123535, - "y": -0.17320507764816284, - "z": 3.648461103439331 - }, - "rotation": { - "w": 0.6444342136383057, - "x": -0.08220034837722778, - "y": -0.6649118661880493, - "z": 0.3684900999069214 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "alpha": 0, - "alphaFinish": 0, - "alphaStart": 0.25, - "clientOnly": false, - "colorFinish": { - "blue": 0, - "green": 0, - "red": 0, - "x": 0, - "y": 0, - "z": 0 - }, - "colorStart": { - "blue": 255, - "green": 255, - "red": 255, - "x": 255, - "y": 255, - "z": 255 - }, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 13.24000072479248, - "green": 13.24000072479248, - "red": 13.24000072479248, - "x": 13.24000072479248, - "y": 13.24000072479248, - "z": 13.24000072479248 - }, - "emitAcceleration": { - "blue": 0, - "green": 0.10000000149011612, - "red": 0, - "x": 0, - "y": 0.10000000149011612, - "z": 0 - }, - "emitDimensions": { - "blue": 1, - "green": 1, - "red": 1, - "x": 1, - "y": 1, - "z": 1 - }, - "emitOrientation": { - "w": 1, - "x": -1.52587890625e-05, - "y": -1.52587890625e-05, - "z": -1.52587890625e-05 - }, - "emitRate": 6, - "emitSpeed": 0, - "emitterShouldTrail": true, - "id": "{e36388d2-7209-4f26-974b-54d314e106a5}", - "lastEdited": 1536093329671654, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", - "lifespan": 10, - "locked": true, - "maxParticles": 10, - "name": "Stars", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "particleRadius": 0.07000000029802322, - "polarFinish": 3.1415927410125732, - "position": { - "blue": 3.78922963142395, - "green": 0.5220947265625, - "red": 1.1928560733795166, - "x": 1.1928560733795166, - "y": 0.5220947265625, - "z": 3.78922963142395 - }, - "queryAACube": { - "scale": 22.932353973388672, - "x": -10.273321151733398, - "y": -10.944082260131836, - "z": -7.676947593688965 - }, - "radiusFinish": 0, - "radiusStart": 0, - "rotation": { - "w": 0.996429443359375, - "x": -1.52587890625e-05, - "y": -0.08442819118499756, - "z": -4.57763671875e-05 - }, - "speedSpread": 0, - "spinFinish": null, - "spinStart": null, - "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", - "type": "ParticleEffect", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, - { - "angularDamping": 0, - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{1dbde8e2-c715-4c09-814e-e411803278c9}", - "lastEdited": 1536093329670778, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", - "locked": true, - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 3.8216662406921387, - "green": 0.0772705078125, - "red": 1.2409718036651611, - "x": 1.2409718036651611, - "y": 0.0772705078125, - "z": 3.8216662406921387 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 1.0677666664123535, - "y": -0.09593456983566284, - "z": 3.648461103439331 - }, - "rotation": { - "w": 0.926024317741394, - "x": 0.20308232307434082, - "y": -0.010269343852996826, - "z": 0.3179827928543091 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 0 - }, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 11.117486953735352, - "green": 3.580313205718994, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 3.580313205718994, - "z": 11.117486953735352 - }, - "id": "{a06f47da-06e5-4bcd-baa2-37816130f2bd}", - "lastEdited": 1536093329671244, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", - "locked": true, - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 6.1806135177612305, - "green": 1.1588134765625, - "red": 1.4755167961120605, - "x": 1.4755167961120605, - "y": 1.1588134765625, - "z": 6.1806135177612305 - }, - "queryAACube": { - "scale": 11.681488037109375, - "x": -4.365227222442627, - "y": -4.6819305419921875, - "z": 0.33986949920654297 - }, - "rotation": { - "w": 0.8637980222702026, - "x": -4.57763671875e-05, - "y": 0.5038070678710938, - "z": -1.52587890625e-05 - }, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, { "alpha": 0, "alphaFinish": 0, @@ -823,7 +401,7 @@ "y": 204, "z": 255 }, - "created": "2018-09-04T20:35:23Z", + "created": "2018-09-05T00:40:03Z", "dimensions": { "blue": 2.5, "green": 2.5, @@ -857,8 +435,8 @@ "emitRate": 2, "emitSpeed": 0, "emitterShouldTrail": true, - "id": "{0b1d2de0-39bf-4416-ac72-aae7ddbb3ff0}", - "lastEdited": 1536093329672220, + "id": "{e62ced49-fa18-4ae1-977f-abef5bc0f3ba}", + "lastEdited": 1536107948775366, "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", "lifespan": 10, "locked": true, @@ -869,16 +447,16 @@ "polarFinish": 3.1415927410125732, "position": { "blue": 3.814434051513672, - "green": 1.44122314453125, - "red": 1.2319090366363525, - "x": 1.2319090366363525, - "y": 1.44122314453125, + "green": 1.2890124320983887, + "red": 1.2254328727722168, + "x": 1.2254328727722168, + "y": 1.2890124320983887, "z": 3.814434051513672 }, "queryAACube": { "scale": 4.330127239227295, - "x": -0.9331545829772949, - "y": -0.7238404750823975, + "x": -0.9396307468414307, + "y": -0.8760511875152588, "z": 1.6493704319000244 }, "radiusFinish": 0.10000000149011612, @@ -896,189 +474,6 @@ "type": "ParticleEffect", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, - { - "angularDamping": 0, - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 255 - }, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 - }, - "id": "{01d9478b-50ae-4f09-b67a-e62a4d133cdf}", - "lastEdited": 1536093329672981, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", - "locked": true, - "name": "Particle Rotate", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 3.8216662406921387, - "green": 0.15618896484375, - "red": 1.2409718036651611, - "x": 1.2409718036651611, - "y": 0.15618896484375, - "z": 3.8216662406921387 - }, - "queryAACube": { - "scale": 0.3464101552963257, - "x": 1.0677666664123535, - "y": -0.017016112804412842, - "z": 3.648461103439331 - }, - "rotation": { - "w": 0.6747081279754639, - "x": -0.06532388925552368, - "y": -0.6342412233352661, - "z": 0.37175559997558594 - }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 0 - }, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 6.9401350021362305, - "green": 0.04553089290857315, - "red": 7.004304885864258, - "x": 7.004304885864258, - "y": 0.04553089290857315, - "z": 6.9401350021362305 - }, - "id": "{718b3532-0c3c-4689-ac41-dca8fedffc4a}", - "lastEdited": 1536093496994604, - "lastEditedBy": "{4eecd88f-ef9b-4a83-bb9a-7f7496209c6b}", - "locked": true, - "name": "floor", - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 3.6175529956817627, - "green": 0.15221074223518372, - "red": 4.108861923217773, - "x": 4.108861923217773, - "y": 0.15221074223518372, - "z": 3.6175529956817627 - }, - "queryAACube": { - "scale": 9.860417366027832, - "x": -0.8213467597961426, - "y": -4.777997970581055, - "z": -1.3126556873321533 - }, - "rotation": { - "w": 0.8660253882408142, - "x": -1.5922749298624694e-05, - "y": 0.5, - "z": -4.572480611386709e-05 - }, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, - { - "clientOnly": false, - "color": { - "blue": 0, - "green": 0, - "red": 0 - }, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 11.117486953735352, - "green": 3.580313205718994, - "red": 0.20000000298023224, - "x": 0.20000000298023224, - "y": 3.580313205718994, - "z": 11.117486953735352 - }, - "id": "{44442e52-e0cf-45d3-9916-678b8aa56a22}", - "lastEdited": 1536093329672691, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", - "locked": true, - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 5.268576622009277, - "green": 1.1588134765625, - "red": 6.100250244140625, - "x": 6.100250244140625, - "y": 1.1588134765625, - "z": 5.268576622009277 - }, - "queryAACube": { - "scale": 11.681488037109375, - "x": 0.2595062255859375, - "y": -4.6819305419921875, - "z": -0.5721673965454102 - }, - "rotation": { - "w": 0.9662165641784668, - "x": -4.57763671875e-05, - "y": -0.2576791048049927, - "z": 1.52587890625e-05 - }, - "shape": "Cube", - "type": "Box", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}", - "visible": false - }, - { - "clientOnly": false, - "created": "2018-09-04T20:35:23Z", - "dimensions": { - "blue": 14.40000057220459, - "green": 14.40000057220459, - "red": 14.40000057220459, - "x": 14.40000057220459, - "y": 14.40000057220459, - "z": 14.40000057220459 - }, - "id": "{8a045310-b82f-4021-9963-0b98d317995c}", - "lastEdited": 1536093329670891, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", - "locked": true, - "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", - "position": { - "blue": 2.3440732955932617, - "green": 1.7684326171875, - "red": 1.8812973499298096, - "x": 1.8812973499298096, - "y": 1.7684326171875, - "z": 2.3440732955932617 - }, - "queryAACube": { - "scale": 24.9415340423584, - "x": -10.589469909667969, - "y": -10.7023344039917, - "z": -10.126693725585938 - }, - "rotation": { - "w": 0.8697794675827026, - "x": -1.52587890625e-05, - "y": 0.4933699369430542, - "z": -4.57763671875e-05 - }, - "shapeType": "box", - "skyboxMode": "enabled", - "type": "Zone", - "userData": "{\"grabbableKey\":{\"grabbable\":false}}" - }, { "alpha": 0, "alphaFinish": 0, @@ -1100,7 +495,7 @@ "y": 255, "z": 255 }, - "created": "2018-09-04T20:35:23Z", + "created": "2018-09-05T00:40:03Z", "dimensions": { "blue": 13.24000072479248, "green": 13.24000072479248, @@ -1133,8 +528,8 @@ }, "emitRate": 6, "emitSpeed": 0, - "id": "{b310070f-6af8-4c1d-9ad7-ea1070b53763}", - "lastEdited": 1536093329670211, + "id": "{298c0571-cbd8-487b-8640-64037d6a8414}", + "lastEdited": 1536107948776382, "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", "lifespan": 10, "locked": true, @@ -1145,16 +540,16 @@ "polarFinish": 3.1415927410125732, "position": { "blue": 1.3712034225463867, - "green": 0.5220947265625, - "red": 2.6281180381774902, - "x": 2.6281180381774902, - "y": 0.5220947265625, + "green": 0.3698839843273163, + "red": 2.6216418743133545, + "x": 2.6216418743133545, + "y": 0.3698839843273163, "z": 1.3712034225463867 }, "queryAACube": { "scale": 22.932353973388672, - "x": -8.838058471679688, - "y": -10.944082260131836, + "x": -8.844534873962402, + "y": -11.096293449401855, "z": -10.09497356414795 }, "radiusFinish": 0, @@ -1173,56 +568,368 @@ "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }, { - "angularDamping": 0, + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.1097896099090576, + "green": 0.04847164824604988, + "red": 1.458284616470337, + "x": 1.458284616470337, + "y": 0.04847164824604988, + "z": 2.1097896099090576 + }, + "id": "{6625dbb8-ff25-458d-a92e-644b58460604}", + "lastEdited": 1536107948776195, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal1.fbx", + "name": "Try Again", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.946338653564453, + "green": 0.09449335932731628, + "red": 1.594836711883545, + "x": 1.594836711883545, + "y": 0.09449335932731628, + "z": 3.946338653564453 + }, + "queryAACube": { + "scale": 2.5651814937591553, + "x": 0.3122459650039673, + "y": -1.188097357749939, + "z": 2.663747787475586 + }, + "rotation": { + "w": 0.8220492601394653, + "x": -1.52587890625e-05, + "y": 0.5693598985671997, + "z": -0.0001068115234375 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/tryAgainEntityScript.js", + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 0.06014331430196762, + "green": 2.582186460494995, + "red": 2.582186698913574, + "x": 2.582186698913574, + "y": 2.582186460494995, + "z": 0.06014331430196762 + }, + "id": "{dfe92dce-f09d-4e9e-b3ed-c68ecd4d476f}", + "lastEdited": 1536108160862286, + "lastEditedBy": "{4656d4a8-5e61-4230-ab34-2888d7945bd6}", + "locked": true, + "modelURL": "http://hifi-content.s3.amazonaws.com/wayne/models/oopsDialog_protocol.fbx", + "name": "Oops Dialog", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.45927095413208, + "green": 1.6763916015625, + "red": 0, + "x": 0, + "y": 1.6763916015625, + "z": 1.45927095413208 + }, + "queryAACube": { + "scale": 3.6522583961486816, + "x": -1.8261291980743408, + "y": -0.14973759651184082, + "z": -0.36685824394226074 + }, + "rotation": { + "w": 0.8684672117233276, + "x": -4.57763671875e-05, + "y": 0.4957197904586792, + "z": -7.62939453125e-05 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/oopsEntityScript.js", + "scriptTimestamp": 1536102551825, + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { "clientOnly": false, "color": { "blue": 0, "green": 0, - "red": 255 + "red": 0 }, - "created": "2018-09-04T20:35:23Z", + "created": "2018-09-05T00:40:03Z", "dimensions": { - "blue": 0.20000000298023224, - "green": 0.20000000298023224, + "blue": 11.117486953735352, + "green": 3.580313205718994, "red": 0.20000000298023224, "x": 0.20000000298023224, - "y": 0.20000000298023224, - "z": 0.20000000298023224 + "y": 3.580313205718994, + "z": 11.117486953735352 }, - "id": "{7c24a30b-47d3-4cf6-af92-5a7959a3cb5d}", - "lastEdited": 1536093329671011, - "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "id": "{144a8cf4-b0e8-489a-9403-d74d4dc4cb3e}", + "lastEdited": 1536107948775774, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", "locked": true, - "name": "Particle Rotate", + "name": "rightWall", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { - "blue": 1.3116319179534912, - "green": 0, - "red": 2.705613613128662, - "x": 2.705613613128662, - "y": 0, - "z": 1.3116319179534912 + "blue": 0, + "green": 1.0061144828796387, + "red": 4.965089321136475, + "x": 4.965089321136475, + "y": 1.0061144828796387, + "z": 0 }, "queryAACube": { - "scale": 0.3464101552963257, - "x": 2.5324084758758545, - "y": -0.17320507764816284, - "z": 1.1384267807006836 + "scale": 11.681488037109375, + "x": -0.8756546974182129, + "y": -4.834629535675049, + "z": -5.8407440185546875 }, "rotation": { - "w": 0.9127794504165649, - "x": 0.2575265169143677, - "y": 0.15553522109985352, - "z": 0.2761729955673218 + "w": 0.8637980222702026, + "x": -4.57763671875e-05, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 }, - "script": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/rotate.js", - "scriptTimestamp": 1532724769253, "shape": "Cube", "type": "Box", "userData": "{\"grabbableKey\":{\"grabbable\":false}}", "visible": false + }, + { + "clientOnly": false, + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 1.159199833869934, + "green": 2.8062009811401367, + "red": 1.6216505765914917, + "x": 1.6216505765914917, + "y": 2.8062009811401367, + "z": 1.159199833869934 + }, + "id": "{37f53408-3d0c-42a5-9891-e6c40a227349}", + "lastEdited": 1536107948775010, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "name": "Back Zone", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.8632707595825195, + "green": 1.6500625610351562, + "red": 3.3211965560913086, + "x": 3.3211965560913086, + "y": 1.6500625610351562, + "z": 1.8632707595825195 + }, + "queryAACube": { + "scale": 3.4421300888061523, + "x": 1.6001315116882324, + "y": -0.07100248336791992, + "z": 0.14220571517944336 + }, + "rotation": { + "w": 0.9304176568984985, + "x": 0, + "y": -0.36650121212005615, + "z": 0 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/zoneBackEntityScript.js", + "shapeType": "box", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{aa6e680c-6750-4776-95bc-ef3118cace5c}", + "lastEdited": 1536107948775945, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "frontWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 2.662257671356201, + "green": 1.0063786506652832, + "red": 1.4868733882904053, + "x": 1.4868733882904053, + "y": 1.0063786506652832, + "z": 2.662257671356201 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -4.353870391845703, + "y": -4.834365367889404, + "z": -3.1784863471984863 + }, + "rotation": { + "w": 0.9666743278503418, + "x": -4.57763671875e-05, + "y": -0.2560006380081177, + "z": 1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.1097896099090576, + "green": 0.04847164824604988, + "red": 1.458284616470337, + "x": 1.458284616470337, + "y": 0.04847164824604988, + "z": 2.1097896099090576 + }, + "id": "{303631f1-04f3-42a6-b8a8-8dd4b65d1231}", + "lastEdited": 1536107948776513, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal2.fbx", + "name": "Back", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.5835940837860107, + "green": 0.09449335932731628, + "red": 3.028078079223633, + "x": 3.028078079223633, + "y": 0.09449335932731628, + "z": 1.5835940837860107 + }, + "queryAACube": { + "scale": 2.5651814937591553, + "x": 1.7454873323440552, + "y": -1.188097357749939, + "z": 0.3010033369064331 + }, + "rotation": { + "w": 0.9084458351135254, + "x": -1.52587890625e-05, + "y": 0.4179598093032837, + "z": -0.0001068115234375 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/backEntityScript.js", + "scriptTimestamp": 1535751754379, + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 0.25, + "clientOnly": false, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255, + "x": 255, + "y": 255, + "z": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 13.24000072479248, + "green": 13.24000072479248, + "red": 13.24000072479248, + "x": 13.24000072479248, + "y": 13.24000072479248, + "z": 13.24000072479248 + }, + "emitAcceleration": { + "blue": 0, + "green": 0.10000000149011612, + "red": 0, + "x": 0, + "y": 0.10000000149011612, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "emitRate": 6, + "emitSpeed": 0, + "emitterShouldTrail": true, + "id": "{8ded39e6-303c-48f2-be79-81b715cca9f7}", + "lastEdited": 1536107948777127, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 10, + "name": "Stars", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.07000000029802322, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 3.78922963142395, + "green": 0.3698839843273163, + "red": 1.1863799095153809, + "x": 1.1863799095153809, + "y": 0.3698839843273163, + "z": 3.78922963142395 + }, + "queryAACube": { + "scale": 22.932353973388672, + "x": -10.279796600341797, + "y": -11.096293449401855, + "z": -7.676947593688965 + }, + "radiusFinish": 0, + "radiusStart": 0, + "rotation": { + "w": 0.996429443359375, + "x": -1.52587890625e-05, + "y": -0.08442819118499756, + "z": -4.57763671875e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" } ], - "Id": "{c9a2e576-13f3-44fa-8d41-a466b33d16c6}", + "Id": "{18abccad-2d57-4176-9d89-24dc424916f5}", "Version": 93 } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e50473bf29..9405915bcf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1211,6 +1211,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo auto discoverabilityManager = DependencyManager::get(); connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); + connect(&domainHandler, &DomainHandler::domainConnectionErrorChanged, DependencyManager::get().data(), + &AddressManager::setLastDomainConnectionError); connect(&locationUpdateTimer, &QTimer::timeout, DependencyManager::get().data(), &AddressManager::storeCurrentAddress); locationUpdateTimer.start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 62cd79a609..1b258e14b1 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -343,9 +343,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { _previousAPILookup.clear(); _shareablePlaceName.clear(); - if (lookupUrl.toString() != REDIRECT_HIFI_ADDRESS) { - setDomainInfo(lookupUrl, trigger); - } + setDomainInfo(lookupUrl, trigger); emit lookupResultsFinished(); QString path = DOMAIN_SPAWNING_POINT; diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index fab2bdd6cb..7226e92451 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -56,6 +56,8 @@ const QString GET_PLACE = "/api/v1/places/%1"; * Read-only. * @property {boolean} isConnected - true if you're connected to the domain in your current href * metaverse address, otherwise false. + * @property {Window.ConnectionRefusedReason} lastDomainConnectionError - The last error saved from connecting to a domain + * unsuccessfully. * Read-only. * @property {string} pathname - The location and orientation in your current href metaverse address * (e.g., "/15,-10,26/0,0,0,1"). @@ -74,6 +76,7 @@ class AddressManager : public QObject, public Dependency { Q_PROPERTY(QUrl href READ currentShareableAddress) Q_PROPERTY(QString protocol READ getProtocol) Q_PROPERTY(QString hostname READ getHost) + Q_PROPERTY(int lastDomainConnectionError READ getLastDomainConnectionError) Q_PROPERTY(QString pathname READ currentPath) Q_PROPERTY(QString placename READ getPlaceName) Q_PROPERTY(QString domainID READ getDomainID) @@ -141,7 +144,8 @@ public: * * @typedef {number} location.LookupTrigger */ - enum LookupTrigger { + enum LookupTrigger + { UserInput, Back, Forward, @@ -184,6 +188,9 @@ public: QUrl getDomainURL() { return _domainURL; } + int getLastDomainConnectionError() { return _lastDomainConnectionError; } + void setLastDomainConnectionError(int reasonCode) { _lastDomainConnectionError = reasonCode; } + public slots: /**jsdoc * Go to a specified metaverse address. @@ -195,7 +202,7 @@ public slots: * Helps ensure that user's location history is correctly maintained. */ void handleLookupString(const QString& lookupString, bool fromSuggestions = false); - + /**jsdoc * Go to a position and orientation resulting from a lookup for a named path in the domain (set in the domain server's * settings). @@ -208,8 +215,9 @@ public slots: // functions and signals that should be exposed are moved to a scripting interface class. // // we currently expect this to be called from NodeList once handleLookupString has been called with a path - bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) - { return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); } + bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) { + return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); + } /**jsdoc * Go back to the previous location in your navigation history, if there is one. @@ -230,8 +238,10 @@ public slots: * @param {location.LookupTrigger} trigger=StartupFromSettings - The reason for the function call. Helps ensure that user's * location history is correctly maintained. */ - void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); } - + void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { + handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); + } + /**jsdoc * Go to the default "welcome" metaverse address. * @function location.goToEntry @@ -362,7 +372,8 @@ signals: * location.locationChangeRequired.connect(onLocationChangeRequired); */ void locationChangeRequired(const glm::vec3& newPosition, - bool hasOrientationChange, const glm::quat& newOrientation, + bool hasOrientationChange, + const glm::quat& newOrientation, bool shouldFaceLocation); /**jsdoc @@ -433,7 +444,7 @@ private slots: void handleShareableNameAPIResponse(QNetworkReply* requestReply); private: - void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply* reply); + void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply* reply); // Set host and port, and return `true` if it was changed. bool setHost(const QString& host, LookupTrigger trigger, quint16 port = 0); @@ -445,8 +456,11 @@ private: bool handleNetworkAddress(const QString& lookupString, LookupTrigger trigger, bool& hostChanged); void handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly = false); - bool handleViewpoint(const QString& viewpointString, bool shouldFace, LookupTrigger trigger, - bool definitelyPathOnly = false, const QString& pathString = QString()); + bool handleViewpoint(const QString& viewpointString, + bool shouldFace, + LookupTrigger trigger, + bool definitelyPathOnly = false, + const QString& pathString = QString()); bool handleUsername(const QString& lookupString); bool handleDomainID(const QString& host); @@ -457,6 +471,8 @@ private: QUrl _domainURL; QUrl _lastVisitedURL; + // domain connection error from domain handler. + int _lastDomainConnectionError{ -1 }; QUuid _rootPlaceID; PositionGetter _positionGetter; @@ -473,4 +489,4 @@ private: QUrl _previousAPILookup; }; -#endif // hifi_AddressManager_h +#endif // hifi_AddressManager_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 0febe1e155..75d670cd3d 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -480,6 +480,7 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer Date: Tue, 4 Sep 2018 18:08:34 -0700 Subject: [PATCH 236/744] Unused variable from merge --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 7d1ac25469..81ee9c18f8 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -440,7 +440,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); for (const auto& sortedAvatar : sortedAvatarVector) { - const auto& avatarData = sortedAvatar.getAvatar(); const Node* otherNode = sortedAvatar.getNode(); auto lastEncodeForOther = sortedAvatar.getTimestamp(); remainingAvatars--; From 2e17140c07e4ff12f44d56be082d73f9438e96b6 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 4 Sep 2018 18:24:01 -0700 Subject: [PATCH 237/744] fixing loadServerlessDomain function to be from 404 redirect --- interface/src/Application.cpp | 2 +- interface/src/Application.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b4a8c43be7..91f064dfa2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3518,7 +3518,7 @@ void Application::setIsServerlessMode(bool serverlessDomain) { void Application::loadServerlessDomain(QUrl domainURL, bool errorDomain) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL)); + QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL), Q_ARG(bool, errorDomain)); return; } diff --git a/interface/src/Application.h b/interface/src/Application.h index 35f998a04d..c82dae3b05 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -436,7 +436,6 @@ public slots: void setIsServerlessMode(bool serverlessDomain); void loadServerlessDomain(QUrl domainURL, bool errorDomain = false); void setIsInterstitialMode(bool interstialMode); - void loadServerlessDomain(QUrl domainURL); void updateVerboseLogging(); From 1ce6c84c089e55c9c3f8118cb4b760ef4cadfbda Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 4 Sep 2018 18:24:49 -0700 Subject: [PATCH 238/744] updating metacall for loadServerlessDomain --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9405915bcf..1104e153df 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3479,7 +3479,7 @@ void Application::setIsServerlessMode(bool serverlessDomain) { void Application::loadServerlessDomain(QUrl domainURL, bool errorDomain) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL)); + QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL), Q_ARG(bool, errorDomain)); return; } From 98c4d05b4ada1a76c469363769e9078054fea8ed Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 4 Sep 2018 19:54:30 -0700 Subject: [PATCH 239/744] fixing typo in domain handler --- libraries/networking/src/DomainHandler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 2784605961..5e9d0f14e9 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -84,7 +84,7 @@ public: void connectedToServerless(std::map namedPaths); - void DomainHandler::loadedErrorDomain(std::map namedPaths); + void loadedErrorDomain(std::map namedPaths); QString getViewPointFromNamedPath(QString namedPath); From 0be7a5ffaa92949954a0c5a2148eb9eb573bfdfd Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 4 Sep 2018 19:58:56 -0700 Subject: [PATCH 240/744] fixing typo in domain handler --- libraries/networking/src/DomainHandler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 2784605961..5e9d0f14e9 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -84,7 +84,7 @@ public: void connectedToServerless(std::map namedPaths); - void DomainHandler::loadedErrorDomain(std::map namedPaths); + void loadedErrorDomain(std::map namedPaths); QString getViewPointFromNamedPath(QString namedPath); From af1c473a9c62ae0039f4705d2bbfb8b3d2f754b7 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 4 Sep 2018 22:17:33 -0700 Subject: [PATCH 241/744] updating with models in meshes folder --- .../meshes/redirect/oopsDialog_auth.fbx | Bin 0 -> 32304 bytes .../meshes/redirect/oopsDialog_auth.png | Bin 0 -> 5012 bytes .../meshes/redirect/oopsDialog_protocol.fbx | Bin 0 -> 32256 bytes .../meshes/redirect/oopsDialog_protocol.png | Bin 0 -> 4899 bytes .../meshes/redirect/oopsDialog_vague.fbx | Bin 0 -> 32480 bytes .../meshes/redirect/oopsDialog_vague.png | Bin 0 -> 5139 bytes interface/resources/serverless/redirect.json | 4 +- scripts/system/oopsEntityScript.js | 41 ++++++++++++++++++ 8 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 interface/resources/meshes/redirect/oopsDialog_auth.fbx create mode 100644 interface/resources/meshes/redirect/oopsDialog_auth.png create mode 100644 interface/resources/meshes/redirect/oopsDialog_protocol.fbx create mode 100644 interface/resources/meshes/redirect/oopsDialog_protocol.png create mode 100644 interface/resources/meshes/redirect/oopsDialog_vague.fbx create mode 100644 interface/resources/meshes/redirect/oopsDialog_vague.png create mode 100644 scripts/system/oopsEntityScript.js diff --git a/interface/resources/meshes/redirect/oopsDialog_auth.fbx b/interface/resources/meshes/redirect/oopsDialog_auth.fbx new file mode 100644 index 0000000000000000000000000000000000000000..3360de993f31b3b2ba600056ed9cd08365c7a473 GIT binary patch literal 32304 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 S-ml1ASn^NHLe=U)E&dNrPE8j8 literal 0 HcmV?d00001 diff --git a/interface/resources/meshes/redirect/oopsDialog_auth.png b/interface/resources/meshes/redirect/oopsDialog_auth.png new file mode 100644 index 0000000000000000000000000000000000000000..2165cfbfa997d6af794c5f1741e7d0dabd421efc GIT binary patch literal 5012 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
-
- +
+
diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index c53fab88a5..3e88c2fd20 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -126,8 +126,7 @@ // Button icons. MUTE_ON_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-mute-a.svg", MUTE_OFF_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-unmute-i.svg", - BUBBLE_ON_ICON = Script.resourcesPath() + "icons/tablet-icons/bubble-a.svg", - BUBBLE_OFF_ICON = Script.resourcesPath() + "icons/tablet-icons/bubble-i.svg", + GOTO_ICON = Script.resourcesPath() + "icons/tablet-icons/goto-i.svg", // Expansion to tablet. MINI_EXPAND_HANDLES = [ // Normalized coordinates in range [-0.5, 0.5] about center of mini tablet. @@ -149,14 +148,14 @@ miniTargetWidth, miniTargetLocalRotation, - // EventBridge + // EventBridge. READY_MESSAGE = "ready", // Engine <== Dialog HOVER_MESSAGE = "hover", // Engine <== Dialog MUTE_MESSAGE = "mute", // Engine <=> Dialog - BUBBLE_MESSAGE = "bubble", // Engine <=> Dialog + GOTO_MESSAGE = "goto", // Engine <=> Dialog EXPAND_MESSAGE = "expand", // Engine <== Dialog - // Sounds + // Sounds. HOVER_SOUND = "./assets/sounds/button-hover.wav", HOVER_VOLUME = 0.5, CLICK_SOUND = "./assets/sounds/button-click.wav", @@ -174,12 +173,10 @@ })); } - function updateBubbleStatus() { - var isBubbleOn = Users.getIgnoreRadiusEnabled(); + function setGotoIcon() { miniOverlayObject.emitScriptEvent(JSON.stringify({ - type: BUBBLE_MESSAGE, - on: isBubbleOn, - icon: isBubbleOn ? BUBBLE_ON_ICON : BUBBLE_OFF_ICON + type: GOTO_MESSAGE, + icon: GOTO_ICON })); } @@ -197,7 +194,7 @@ case READY_MESSAGE: // Send initial button statuses. updateMutedStatus(); - updateBubbleStatus(); + setGotoIcon(); break; case HOVER_MESSAGE: // Audio feedback. @@ -208,15 +205,15 @@ playSound(clickSound, CLICK_VOLUME); Audio.muted = !Audio.muted; break; - case BUBBLE_MESSAGE: - // Toggle bubble. + case GOTO_MESSAGE: + // Goto. playSound(clickSound, CLICK_VOLUME); - Users.toggleIgnoreRadius(); + miniState.setState(miniState.MINI_EXPANDING, { hand: uiHand, goto: true }); break; case EXPAND_MESSAGE: // Expand tablet; playSound(clickSound, CLICK_VOLUME); - miniState.setState(miniState.MINI_EXPANDING, uiHand); + miniState.setState(miniState.MINI_EXPANDING, { hand: uiHand, goto: false }); break; } } @@ -419,7 +416,6 @@ getMiniTabletID: getMiniTabletID, getMiniTabletProperties: getMiniTabletProperties, updateMutedStatus: updateMutedStatus, - updateBubbleStatus: updateBubbleStatus, show: show, size: size, startExpandingTablet: startExpandingTablet, @@ -470,6 +466,10 @@ miniExpandTimer = null, miniExpandStart, + // Tablet targets. + isGoto = false, + TABLET_ADDRESS_DIALOG = "hifi/tablet/TabletAddressDialog.qml", + // Visibility. MIN_HAND_CAMERA_ANGLE = 30, DEGREES_180 = 180, @@ -483,9 +483,8 @@ updateTimer = null; } - // Stop monitoring mute and bubble changes. + // Stop monitoring mute changes. Audio.mutedChanged.disconnect(ui.updateMutedStatus); - Users.ignoreRadiusEnabledChanged.disconnect(ui.updateBubbleStatus); // Don't keep overlays prepared if in desktop mode. ui.destroy(); @@ -496,9 +495,8 @@ // Create UI so that it's ready to be displayed without seeing artefacts from creating the UI. ui = new UI(); - // Start monitoring mute and bubble changes. + // Start monitoring mute changes. Audio.mutedChanged.connect(ui.updateMutedStatus); - Users.ignoreRadiusEnabledChanged.connect(ui.updateBubbleStatus); // Start updates. updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL); @@ -640,8 +638,9 @@ setState(TABLET_OPEN); } - function enterMiniExpanding(hand) { - ui.startExpandingTablet(hand); + function enterMiniExpanding(data) { + isGoto = data.goto; + ui.startExpandingTablet(data.hand); miniExpandStart = Date.now(); miniExpandTimer = Script.setTimeout(expandMini, MINI_EXPAND_TIMEOUT); } @@ -665,10 +664,17 @@ ui.hide(); + if (isGoto) { + tablet.loadQMLSource(TABLET_ADDRESS_DIALOG); + } else { + tablet.gotoHomeScreen(); + } + Overlays.editOverlay(HMD.tabletID, { position: miniTabletProperties.position, orientation: miniTabletProperties.orientation }); + HMD.openTablet(true); } @@ -807,13 +813,13 @@ if (message.action === "grab" && miniState.getState() === miniState.MINI_VISIBLE) { hand = message.joint === HAND_NAMES[miniHand] ? miniHand : otherHand(miniHand); if (hand === miniHand) { - miniState.setState(miniState.MINI_EXPANDING, hand); + miniState.setState(miniState.MINI_EXPANDING, { hand: hand, goto: false }); } else { miniState.setState(miniState.MINI_GRABBED); } } else if (message.action === "release" && miniState.getState() === miniState.MINI_GRABBED) { hand = message.joint === HAND_NAMES[miniHand] ? miniHand : otherHand(miniHand); - miniState.setState(miniState.MINI_EXPANDING, hand); + miniState.setState(miniState.MINI_EXPANDING, { hand: hand, goto: false }); } } From 12454d7ec74d4489c69d34ffedf5a57cc78a39b2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 7 Sep 2018 16:48:36 +1200 Subject: [PATCH 355/744] Increase mini tablet grow/shrink time --- scripts/system/miniTablet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 3e88c2fd20..226dbc7d9a 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -455,7 +455,7 @@ UPDATE_INTERVAL = 300, // Mini tablet scaling. - MINI_SCALE_DURATION = 150, + MINI_SCALE_DURATION = 250, MINI_SCALE_TIMEOUT = 20, miniScaleTimer = null, miniScaleStart, From 92f218ae29a24edb8ef7a56a62e83a7201998474 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Thu, 6 Sep 2018 22:59:19 -0700 Subject: [PATCH 356/744] Adding margins to forgotten link text --- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 707c173382..90437c008e 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -147,6 +147,7 @@ Item { anchors { right: usernameField.right top: usernameField.bottom + topMargin: 2 } text: "Forgot Username?" @@ -174,6 +175,7 @@ Item { anchors { right: passwordField.right top: passwordField.bottom + topMargin: 2 } text: "Forgot Password?" From 190a335c7118be684db1e86369759207e16cc29a Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Fri, 7 Sep 2018 00:01:00 -0700 Subject: [PATCH 357/744] Adding more margin to forgotten links --- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 90437c008e..28523617b6 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -147,7 +147,7 @@ Item { anchors { right: usernameField.right top: usernameField.bottom - topMargin: 2 + topMargin: 4 } text: "Forgot Username?" @@ -175,7 +175,7 @@ Item { anchors { right: passwordField.right top: passwordField.bottom - topMargin: 2 + topMargin: 4 } text: "Forgot Password?" From f206a8f158e1a938a435326aaf0df5d5702d6ba0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 7 Sep 2018 19:43:51 +1200 Subject: [PATCH 358/744] Display mini tablet on hand being gazed at if there is a choice --- scripts/system/miniTablet.js | 56 +++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 226dbc7d9a..fa3047c92e 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -527,7 +527,10 @@ Vec3.multiplyQbyV(handOrientation, uiPositionAndOrientation.position))); miniOrientation = Quat.multiply(handOrientation, uiPositionAndOrientation.rotation); miniToCameraDirection = Vec3.normalize(Vec3.subtract(Camera.position, miniPosition)); - return Vec3.dot(miniToCameraDirection, Quat.getForward(miniOrientation)) > MIN_HAND_CAMERA_ANGLE_COS; + return { + show: Vec3.dot(miniToCameraDirection, Quat.getForward(miniOrientation)) > MIN_HAND_CAMERA_ANGLE_COS, + cameraToHand: -Vec3.dot(miniToCameraDirection, Quat.getForward(Camera.orientation)) + }; } function enterMiniHidden() { @@ -535,14 +538,27 @@ } function updateMiniHidden() { + var showLeft, + showRight; + // Don't show mini tablet if tablet proper is already displayed or in toolbar mode. if (HMD.showTablet || tablet.toolbarMode) { return; } - // Compare palm directions of hands with vectors from palms to camera. - if (shouldShowMini(LEFT_HAND)) { + + // Show mini tablet if it would be pointing at the camera. + showLeft = shouldShowMini(LEFT_HAND); + 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) { + setState(MINI_SHOWING, LEFT_HAND); + } else { + setState(MINI_SHOWING, RIGHT_HAND); + } + } else if (showLeft.show) { setState(MINI_SHOWING, LEFT_HAND); - } else if (shouldShowMini(RIGHT_HAND)) { + } else if (showRight.show) { setState(MINI_SHOWING, RIGHT_HAND); } } @@ -609,13 +625,38 @@ } function updateMiniVisible() { + var showLeft, + showRight; + // Hide mini tablet if tablet proper has been displayed by other means. if (HMD.showTablet) { setState(MINI_HIDDEN); return; } - // Check that palm direction of mini tablet hand still less than maximum angle. - if (!shouldShowMini(miniHand)) { + + // Check that the mini tablet should still be visible and if so then ensure it's on the hand that the camera is + // gazing at. + showLeft = shouldShowMini(LEFT_HAND); + showRight = shouldShowMini(RIGHT_HAND); + if (showLeft.show && showRight.show) { + if (showLeft.cameraToHand > showRight.cameraToHand) { + if (miniHand !== LEFT_HAND) { + setState(MINI_HIDING); + } + } else { + if (miniHand !== RIGHT_HAND) { + setState(MINI_HIDING); + } + } + } else if (showLeft.show) { + if (miniHand !== LEFT_HAND) { + setState(MINI_HIDING); + } + } else if (showRight.show) { + if (miniHand !== RIGHT_HAND) { + setState(MINI_HIDING); + } + } else { setState(MINI_HIDING); } } @@ -728,7 +769,8 @@ function setState(state, data) { if (state !== miniState) { - debug("State transition from " + STATE_STRINGS[miniState] + " to " + STATE_STRINGS[state]); + debug("State transition from " + STATE_STRINGS[miniState] + " to " + STATE_STRINGS[state] + + ( data ? " " + JSON.stringify(data) : "")); if (STATE_MACHINE[STATE_STRINGS[miniState]].exit) { STATE_MACHINE[STATE_STRINGS[miniState]].exit(data); } From 7058b07aea05226b33b769d18b7ea02c1ebbc39b Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Fri, 7 Sep 2018 12:41:27 +0300 Subject: [PATCH 359/744] FB18024: 2 users report that while in VR and in GoTo menu, keyboard is unable to be minimized --- interface/resources/qml/hifi/tablet/TabletAddressDialog.qml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index c9d05aea51..802c00a97a 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -105,7 +105,6 @@ StackView { propagateComposedEvents: true onPressed: { parent.forceActiveFocus(); - addressBarDialog.keyboardEnabled = false; mouse.accepted = false; } } @@ -223,7 +222,6 @@ StackView { updateLocationText(text.length > 0); } onAccepted: { - addressBarDialog.keyboardEnabled = false; toggleOrGo(); } @@ -378,7 +376,7 @@ StackView { HifiControls.Keyboard { id: keyboard - raised: parent.keyboardEnabled + raised: parent.keyboardEnabled && parent.keyboardRaised numeric: parent.punctuationMode anchors { bottom: parent.bottom From 8d4c47cf8ed70e7ed8c8bd1955ecdbad93cd293d Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 7 Sep 2018 08:49:44 -0700 Subject: [PATCH 360/744] Make template specialization of BaseNestableTransformNode explicit --- interface/src/ui/overlays/OverlayTransformNode.cpp | 1 + .../src/avatars-renderer/AvatarTransformNode.cpp | 1 + libraries/entities/src/EntityTransformNode.cpp | 1 + libraries/shared/src/NestableTransformNode.cpp | 1 + 4 files changed, 4 insertions(+) diff --git a/interface/src/ui/overlays/OverlayTransformNode.cpp b/interface/src/ui/overlays/OverlayTransformNode.cpp index fa499b3ce3..817b6af305 100644 --- a/interface/src/ui/overlays/OverlayTransformNode.cpp +++ b/interface/src/ui/overlays/OverlayTransformNode.cpp @@ -7,6 +7,7 @@ // #include "OverlayTransformNode.h" +template<> glm::vec3 BaseNestableTransformNode::getActualScale(const std::shared_ptr& nestablePointer) const { return nestablePointer->getBounds().getScale(); } \ No newline at end of file diff --git a/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp index bc24b8a570..ca3d4a6062 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp @@ -7,6 +7,7 @@ // #include "AvatarTransformNode.h" +template<> glm::vec3 BaseNestableTransformNode::getActualScale(const std::shared_ptr& nestablePointer) const { return nestablePointer->scaleForChildren(); } \ No newline at end of file diff --git a/libraries/entities/src/EntityTransformNode.cpp b/libraries/entities/src/EntityTransformNode.cpp index 35f4d8e52f..438ece3840 100644 --- a/libraries/entities/src/EntityTransformNode.cpp +++ b/libraries/entities/src/EntityTransformNode.cpp @@ -7,6 +7,7 @@ // #include "EntityTransformNode.h" +template<> glm::vec3 BaseNestableTransformNode::getActualScale(const std::shared_ptr& nestablePointer) const { return nestablePointer->getScaledDimensions(); } \ No newline at end of file diff --git a/libraries/shared/src/NestableTransformNode.cpp b/libraries/shared/src/NestableTransformNode.cpp index 9da2c85aa3..9723f388f6 100644 --- a/libraries/shared/src/NestableTransformNode.cpp +++ b/libraries/shared/src/NestableTransformNode.cpp @@ -8,6 +8,7 @@ #include "NestableTransformNode.h" +template<> glm::vec3 BaseNestableTransformNode::getActualScale(const std::shared_ptr& nestablePointer) const { return nestablePointer->getAbsoluteJointScaleInObjectFrame(_jointIndex); } \ No newline at end of file From 5a059e93184acc28e21262b8080c6341a7559403 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Fri, 7 Sep 2018 09:44:19 -0700 Subject: [PATCH 361/744] Fix for build failure on linux and android. --- libraries/animation/src/AnimContext.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index b30421ae84..c455dd9c8f 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -14,8 +14,8 @@ #include #include -#include -#include +#include +#include #include enum class AnimNodeType { From 3dc993c841566027c7409ca29b98cb35a7e08701 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 7 Sep 2018 09:46:21 -0700 Subject: [PATCH 362/744] Fix small error in translation delta --- libraries/avatars/src/AvatarData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 905569cb65..04efcb242e 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -625,7 +625,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += numValidityBytes; // Move pointer past the validity bytes - float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_ROTATION_DOT; + float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION; float maxTranslationDimension = 0.0; for (int i = 0; i < jointData.size(); i++) { From d979d02139158069201b489ad5a9e674a8cee951 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Fri, 7 Sep 2018 10:03:55 -0700 Subject: [PATCH 363/744] why did i do that? --- libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h b/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h index b9fe125c8d..6b7d3405f0 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h @@ -65,7 +65,7 @@ public: ~GLBuffer(); - virtual void transfer() {}; + virtual void transfer() = 0; protected: GLBuffer(const std::weak_ptr& backend, const Buffer& buffer, GLuint id); From f9ec53a2e6ac9bcd64d5ab0d53c2c868aa7fde27 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 7 Sep 2018 10:25:26 -0700 Subject: [PATCH 364/744] fix bloom on edges --- libraries/render-utils/src/DeferredFramebuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/DeferredFramebuffer.cpp b/libraries/render-utils/src/DeferredFramebuffer.cpp index 2df89b8808..1906375654 100644 --- a/libraries/render-utils/src/DeferredFramebuffer.cpp +++ b/libraries/render-utils/src/DeferredFramebuffer.cpp @@ -73,7 +73,7 @@ void DeferredFramebuffer::allocate() { _deferredFramebufferDepthColor->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); - auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT); + auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT, gpu::Sampler::WRAP_CLAMP); _lightingTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, gpu::Texture::SINGLE_MIP, smoothSampler); _lightingFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("lighting")); From dd6801219b098bf028bfae60b8b78030846878de Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 7 Sep 2018 19:54:26 +0200 Subject: [PATCH 365/744] Create entities from avatar a bit lower to be able to transform them away from the user easier --- scripts/system/edit.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index e340c75a8b..ed2a179613 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1739,7 +1739,6 @@ function getPositionToCreateEntity(extra) { position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); } else { position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); - position.y += 0.5; } if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { From fa72910ac13b7876dcfe606aa38aa262cb38f640 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Fri, 7 Sep 2018 11:14:12 -0700 Subject: [PATCH 366/744] fixing fuckeries --- libraries/fbx/src/FBXReader_Mesh.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index 19bd99ec5d..72f0fc6f23 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -734,8 +734,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { clusterIndices[i] = (uint8_t)(fbxMesh.clusterIndices[i]); } vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); - } - else { + } else { vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) fbxMesh.clusterIndices.constData()); } } @@ -763,15 +762,14 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { bool interleaveNormalsTangents = true; // If has blend shapes allocate and assign buffers for pos and tangents now - hasBlendShapes = true; if (hasBlendShapes) { auto posBuffer = std::make_shared(); posBuffer->setData(positionsSize, (const gpu::Byte*) vertBuffer->getData() + positionsOffset); vertexBufferStream->addBuffer(posBuffer, 0, positionElement.getSize()); - auto tangentBuffer = std::make_shared(); - tangentBuffer->setData(normalsAndTangentsSize, (const gpu::Byte*) vertBuffer->getData() + normalsAndTangentsOffset); - vertexBufferStream->addBuffer(tangentBuffer, 0, normalsAndTangentsStride); + auto normalsAndTangentsBuffer = std::make_shared(); + normalsAndTangentsBuffer->setData(normalsAndTangentsSize, (const gpu::Byte*) vertBuffer->getData() + normalsAndTangentsOffset); + vertexBufferStream->addBuffer(normalsAndTangentsBuffer, 0, normalsAndTangentsStride); // update channels and attribBuffer size accordingly interleavePositions = false; @@ -814,8 +812,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { if (texCoords1Size) { vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, attribChannel, texCoordsElement, bufOffset); bufOffset += texCoordsElement.getSize(); - } - else if (texCoordsSize) { + } else if (texCoordsSize) { vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, attribChannel, texCoordsElement, bufOffset - texCoordsElement.getSize()); } if (clusterIndicesSize) { From d9495275cc8ff0dc701486349ecfe1c82cd75b11 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 7 Sep 2018 11:22:31 -0700 Subject: [PATCH 367/744] adding cancel button and signed in checkbox --- interface/resources/qml/LoginDialog.qml | 1 + .../qml/LoginDialog/LinkAccountBody.qml | 23 ++++++++++++++----- .../resources/qml/windows/ModalFrame.qml | 18 +++++++++++++++ .../resources/qml/windows/ModalWindow.qml | 1 + 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index e21e8b7354..336858502d 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -23,6 +23,7 @@ ModalWindow { objectName: "LoginDialog" implicitWidth: 520 implicitHeight: 320 + closeButtonVisible: true destroyOnCloseButton: true destroyOnHidden: true visible: true diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 2274d88e04..4c9fe6ae94 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -234,6 +234,23 @@ Item { onHeightChanged: d.resize(); onWidthChanged: d.resize(); anchors.horizontalCenter: parent.horizontalCenter + CheckBox { + id: autoLogoutCheckbox; + checked: Settings.getValue("wallet/autoLogout", true); + text: "Keep me signed in" + boxSize: 20; + labelFontSize: 15; + color: hifi.colors.black; + onCheckedChanged: { + Settings.setValue("wallet/autoLogout", !checked); + if (checked) { + Settings.setValue("wallet/savedUsername", ""); + } else { + Settings.setValue("wallet/savedUsername", Account.username); + } + } + } + Button { id: linkAccountButton anchors.verticalCenter: parent.verticalCenter @@ -244,12 +261,6 @@ Item { onClicked: linkAccountBody.login() } - - Button { - anchors.verticalCenter: parent.verticalCenter - text: qsTr("Cancel") - onClicked: root.tryDestroy() - } } Row { diff --git a/interface/resources/qml/windows/ModalFrame.qml b/interface/resources/qml/windows/ModalFrame.qml index 211353b5f3..640069a0b3 100644 --- a/interface/resources/qml/windows/ModalFrame.qml +++ b/interface/resources/qml/windows/ModalFrame.qml @@ -94,5 +94,23 @@ Frame { color: hifi.colors.lightGray } } + + + GlyphButton { + id: closeButton + visible: window.closeButtonVisible + width: 30 + y: -hifi.dimensions.modalDialogTitleHeight + anchors { + top: parent.top + right: parent.right + topMargin: 10 + rightMargin: 10 + } + glyph: hifi.glyphs.close + onClicked: { + window.destroy(); + } + } } } diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml index 2d56099051..75f5c2d646 100644 --- a/interface/resources/qml/windows/ModalWindow.qml +++ b/interface/resources/qml/windows/ModalWindow.qml @@ -19,6 +19,7 @@ ScrollingWindow { destroyOnHidden: true frame: ModalFrame { } + property bool closeButtonVisible: false property int colorScheme: hifi.colorSchemes.light property bool draggable: false From db96603cdabc35034aa393ed750952c854f618ce Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 7 Sep 2018 11:23:58 -0700 Subject: [PATCH 368/744] removing some unused properties --- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 4c9fe6ae94..9bb40fd538 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -21,8 +21,6 @@ Item { height: root.pane.height width: root.pane.width property bool failAfterSignUp: false - property var locale: Qt.locale() - property string dateTimeString function login() { mainTextContainer.visible = false From 1a67176819d5171dba901442d07b7a76b4c2c198 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 7 Sep 2018 11:29:30 -0700 Subject: [PATCH 369/744] Fix a dangling merge conflict --- libraries/render-utils/src/SoftAttachmentModel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index 9ab30c179a..93ce8f595a 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -79,7 +79,6 @@ void SoftAttachmentModel::updateClusterMatrices(bool triggerBlendshapes) { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); if (triggerBlendshapes && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { - if (_blendedVertexBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } From 44d36ec622dd0fee044ebecac11d6c387d4721a6 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 7 Sep 2018 11:31:29 -0700 Subject: [PATCH 370/744] wallet should save username when checked --- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 9bb40fd538..ca81a6b870 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -242,9 +242,9 @@ Item { onCheckedChanged: { Settings.setValue("wallet/autoLogout", !checked); if (checked) { - Settings.setValue("wallet/savedUsername", ""); - } else { Settings.setValue("wallet/savedUsername", Account.username); + } else { + Settings.setValue("wallet/savedUsername", ""); } } } From 39041f294281f249386a63096faaff4195659924 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 7 Sep 2018 11:36:21 -0700 Subject: [PATCH 371/744] fixing username field --- .../resources/qml/LoginDialog/LinkAccountBody.qml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index ca81a6b870..bc0b8fb2e0 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -242,9 +242,9 @@ Item { onCheckedChanged: { Settings.setValue("wallet/autoLogout", !checked); if (checked) { - Settings.setValue("wallet/savedUsername", Account.username); - } else { Settings.setValue("wallet/savedUsername", ""); + } else { + Settings.setValue("wallet/savedUsername", Account.username); } } } @@ -310,14 +310,6 @@ Item { } usernameField.forceActiveFocus(); - - var data = { - "date": new Date().toLocaleString(), - }; - print(new Date().toLocaleString()); - print(model.sessionId); - - //UserActivityLogger.logAction("login_screen_shown", ) } Connections { From 32e2a244a110a7d16a2afd5e319478d6f0f41ac7 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 7 Sep 2018 11:44:35 -0700 Subject: [PATCH 372/744] detailed profile range for avatar unpacking --- libraries/avatars/src/AvatarHashMap.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index c1246866dc..1383939b8b 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -21,6 +21,7 @@ #include "AvatarLogging.h" #include "AvatarTraits.h" +#include "Profile.h" void AvatarReplicas::addReplica(const QUuid& parentID, AvatarSharedPointer replica) { if (parentID == QUuid()) { @@ -214,6 +215,7 @@ AvatarSharedPointer AvatarHashMap::findAvatar(const QUuid& sessionUUID) const { } void AvatarHashMap::processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode) { + DETAILED_PROFILE_RANGE(network, __FUNCTION__); PerformanceTimer perfTimer("receiveAvatar"); // enumerate over all of the avatars in this packet // only add them if mixerWeakPointer points to something (meaning that mixer is still around) From 9b9785d250620a5982c84a817540c7c4ddf5f95f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 7 Sep 2018 10:35:41 -0700 Subject: [PATCH 373/744] Fix entity list not handling deleted entities --- scripts/system/html/js/entityList.js | 44 +++++++++++++--------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index ebf31d5d36..8358ceba4b 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -34,9 +34,6 @@ const COMPARE_DESCENDING = function(a, b) { return COMPARE_ASCENDING(b, a); } -//console.log = function() { }; - - // List of all entities let entities = [] @@ -49,7 +46,7 @@ var selectedEntities = []; var currentSortColumn = 'type'; var currentSortOrder = ASCENDING_SORT; -const ENABLE_PROFILING = true; +const ENABLE_PROFILING = false; var profileIndent = ''; const PROFILE = !ENABLE_PROFILING ? function() { } : function(name, fn, args) { console.log("PROFILE-Web " + profileIndent + "(" + name + ") Begin"); @@ -62,10 +59,6 @@ const PROFILE = !ENABLE_PROFILING ? function() { } : function(name, fn, args) { console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); }; -debugPrint = function (message) { - console.log(message); -}; - function loaded() { openEventBridge(function() { elEntityTable = document.getElementById("entity-table"); @@ -127,7 +120,6 @@ function loaded() { function onRowClicked(clickEvent) { let entityID = this.dataset.entityID; - console.log("CLICKED", entityID, this); let selection = [entityID]; if (clickEvent.ctrlKey) { let selectedIndex = selectedEntities.indexOf(entityID); @@ -212,7 +204,6 @@ function loaded() { // Update the entity list with the new set of data sent from edit.js function updateEntityList(entityData) { - console.warn("updating entity list"); const IMAGE_MODEL_NAME = 'default-image-model.fbx'; entities = [] @@ -291,13 +282,11 @@ function loaded() { function refreshEntityList() { PROFILE("refresh-entity-list", function() { - console.warn("refreshing entity list"); PROFILE("filter", function() { let searchTerm = elFilter.value; if (searchTerm === '') { visibleEntities = entities.slice(0); } else { - console.log("Filtering on: ", searchTerm); visibleEntities = entities.filter(function(e) { return e.name.indexOf(searchTerm) > -1 || e.type.indexOf(searchTerm) > -1 @@ -308,7 +297,6 @@ function loaded() { PROFILE("sort", function() { let cmp = currentSortOrder === ASCENDING_SORT ? COMPARE_ASCENDING : COMPARE_DESCENDING; - console.log("Doing sort", currentSortColumn, currentSortOrder); visibleEntities.sort(cmp); }); @@ -322,16 +310,27 @@ function loaded() { } function removeEntities(deletedIDs) { - return; - for (i = 0, length = deletedIDs.length; i < length; i++) { - let id = deletedIDs[i]; - entities[id].el.remove(); - delete entities[id]; + // Loop from the back so we can pop items off while iterating + for (let j = entities.length - 1; j >= 0; --j) { + let id = entities[j]; + for (let i = 0, length = deletedIDs.length; i < length; ++i) { + if (id === deletedIDs[i]) { + entities.splice(j, 1); + entitiesByID[id].el.remove(); + delete entitiesByID[id]; + break; + } + } } + refreshEntities(); } function clearEntities() { - entities = {}; + entities = [] + entitiesByID = {}; + visibleEntities = []; + elEntityTableBody.innerHTML = ''; + refreshFooter(); } @@ -351,14 +350,14 @@ function loaded() { } function setSortColumn(column) { PROFILE("set-sort-column", function() { - if (currentSortColumn == column) { + if (currentSortColumn === column) { currentSortOrder *= -1; } else { elSortOrder[currentSortColumn].innerHTML = ""; currentSortColumn = column; currentSortOrder = ASCENDING_SORT; } - elSortOrder[column].innerHTML = currentSortOrder == ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING; + elSortOrder[column].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING; refreshEntityList(); }); } @@ -485,11 +484,8 @@ function loaded() { elFooter.firstChild.nodeValue = "0 entities found"; } else if (newEntities) { elNoEntitiesMessage.style.display = "none"; - //entityData = newEntities; updateEntityList(newEntities); - //refreshEntityList(); updateSelectedEntities(data.selectedIDs); - //resize(); } }); } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { From 3cc90d3c80f9696276902d97e616cb7ec9596d2a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 7 Sep 2018 12:07:19 -0700 Subject: [PATCH 374/744] Disable profiling in entityList.js --- scripts/system/libraries/entityList.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 3900260d0c..06255e38ae 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -11,8 +11,9 @@ /* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages, cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible */ +var PROFILING_ENABLED = false; var profileIndent = ''; -PROFILE = function(name, fn, args) { +PROFILE = !PROFILING_ENABLED ? function() { } : function(name, fn, args) { console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin"); var previousIndent = profileIndent; profileIndent += ' '; @@ -21,7 +22,7 @@ PROFILE = function(name, fn, args) { var delta = Date.now() - before; profileIndent = previousIndent; console.log("PROFILE-Script " + profileIndent + "(" + name + ") End " + delta + "ms"); -} +}; EntityListTool = function(shouldUseEditTabletApp) { var that = {}; @@ -82,7 +83,6 @@ EntityListTool = function(shouldUseEditTabletApp) { PROFILE("Script-JSON.stringify", function() { dataString = JSON.stringify(data); }); - console.log("Length: ", dataString.length, data.type); PROFILE("Script-emitScriptEvent", function() { webView.emitScriptEvent(dataString); if (entityListWindow.window) { @@ -90,7 +90,7 @@ EntityListTool = function(shouldUseEditTabletApp) { } }); } - + that.toggleVisible = function() { that.setVisible(!visible); }; @@ -121,7 +121,7 @@ EntityListTool = function(shouldUseEditTabletApp) { selectedIDs: selectedIDs }); }; - + that.deleteEntities = function (deletedIDs) { emitJSONScriptEvent({ type: "deleted", @@ -150,7 +150,6 @@ EntityListTool = function(shouldUseEditTabletApp) { PROFILE("getProperties", function() { for (var i = 0; i < ids.length; i++) { var id = ids[i]; - //var properties = Entities.getEntityProperties(id); var properties = Entities.getEntityProperties(id, ['name', 'type', 'locked', 'visible', 'renderInfo', 'type', 'modelURL', 'materialURL', 'script']); @@ -168,16 +167,16 @@ EntityListTool = function(shouldUseEditTabletApp) { url: url, locked: properties.locked, visible: properties.visible, - verticesCount: (properties.renderInfo !== undefined ? + verticesCount: (properties.renderInfo !== undefined ? valueIfDefined(properties.renderInfo.verticesCount) : ""), - texturesCount: (properties.renderInfo !== undefined ? + texturesCount: (properties.renderInfo !== undefined ? valueIfDefined(properties.renderInfo.texturesCount) : ""), - texturesSize: (properties.renderInfo !== undefined ? + texturesSize: (properties.renderInfo !== undefined ? valueIfDefined(properties.renderInfo.texturesSize) : ""), - hasTransparent: (properties.renderInfo !== undefined ? + hasTransparent: (properties.renderInfo !== undefined ? valueIfDefined(properties.renderInfo.hasTransparent) : ""), isBaked: properties.type === "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false, - drawCalls: (properties.renderInfo !== undefined ? + drawCalls: (properties.renderInfo !== undefined ? valueIfDefined(properties.renderInfo.drawCalls) : ""), hasScript: properties.script !== "" }); @@ -212,8 +211,7 @@ EntityListTool = function(shouldUseEditTabletApp) { try { data = JSON.parse(data); } catch(e) { - console.log(data); - //print("entityList.js: Error parsing JSON: " + e.name + " data " + data); + print("entityList.js: Error parsing JSON: " + e.name + " data " + data); return; } From d09391faa5f1247c127e554e9e8c8438cdfe5749 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 7 Sep 2018 12:17:40 -0700 Subject: [PATCH 375/744] Fix invisible error --- interface/src/avatar/MyAvatar.cpp | 1 + .../controllers/controllerModules/teleport.js | 104 +++++------------- 2 files changed, 31 insertions(+), 74 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9a78f394e1..1a5f8804e4 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3315,6 +3315,7 @@ QVariantMap MyAvatar::getCollisionCapsule() { capsule["start"] = vec3toVariant(start); capsule["end"] = vec3toVariant(end); capsule["radius"] = QVariant(radius); + capsule["scale"] = QVariant(getModelScale()); return capsule; } diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 92fc91094d..02b470d332 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -22,7 +22,6 @@ Script.include("/~/system/libraries/controllers.js"); (function() { // BEGIN LOCAL_SCOPE var TARGET_MODEL_URL = Script.resolvePath("../../assets/models/teleport-destination.fbx"); - var CANCEL_MODEL_URL = Script.resolvePath("../../assets/models/teleport-cancel.fbx"); var SEAT_MODEL_URL = Script.resolvePath("../../assets/models/teleport-seat.fbx"); var TARGET_MODEL_DIMENSIONS = { @@ -63,28 +62,26 @@ Script.include("/~/system/libraries/controllers.js"); alpha: 1, width: 0.025 }; + var teleportPath = { color: COLORS_TELEPORT_CAN_TELEPORT, alpha: 1, width: 0.025 }; + var seatPath = { color: COLORS_TELEPORT_SEAT, alpha: 1, width: 0.025 }; - var cancelEnd = { - type: "model", - url: CANCEL_MODEL_URL, - dimensions: TARGET_MODEL_DIMENSIONS, - ignorePickIntersection: true - }; + var teleportEnd = { type: "model", url: TARGET_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, ignorePickIntersection: true }; + var seatEnd = { type: "model", url: SEAT_MODEL_URL, @@ -93,19 +90,20 @@ Script.include("/~/system/libraries/controllers.js"); }; var collisionEnd = { - type: "sphere", - dimensions: {x: 0, y: 0, z: 0}, + type: "shape", + shape: "box", + dimensions: TARGET_MODEL_DIMENSIONS, + alpha: 0.0, ignorePickIntersection: true }; - var teleportRenderStates = [{name: "noend", path: cancelPath}, - {name: "cancel", path: cancelPath, end: cancelEnd}, + var teleportRenderStates = [{name: "cancel", path: cancelPath}, {name: "teleport", path: teleportPath, end: teleportEnd}, {name: "seat", path: seatPath, end: seatEnd}, {name: "invisible", end: collisionEnd}]; var DEFAULT_DISTANCE = 8.0; - var teleportDefaultRenderStates = [{name: "noend", distance: DEFAULT_DISTANCE, path: cancelPath}]; + var teleportDefaultRenderStates = [{name: "cancel", distance: DEFAULT_DISTANCE, path: cancelPath}]; var ignoredEntities = []; @@ -117,9 +115,9 @@ Script.include("/~/system/libraries/controllers.js"); var TARGET = { NONE: 'none', // Not currently targetting anything - INVISIBLE: 'invisible', // The current target is an invvsible surface + INVISIBLE: 'invisible', // The current target is an invisible surface INVALID: 'invalid', // The current target is invalid (wall, ceiling, etc.) - CANCEL: 'cancel', // Insufficient space to accommodate the avatar capsule + COLLIDES: 'collides', // Insufficient space to accommodate the avatar capsule SURFACE: 'surface', // The current target is a valid surface SEAT: 'seat' // The current target is a seat }; @@ -205,20 +203,20 @@ Script.include("/~/system/libraries/controllers.js"); this.teleportHandCollisionPick; this.recreateCollisionPicks = function() { - if (_this.teleportHandCollisionPick !== undefined) { Picks.removePick(_this.teleportHandCollisionPick); } if (_this.teleportHeadCollisionPick !== undefined) { Picks.removePick(_this.teleportHeadCollisionPick); } + var capsuleData = MyAvatar.getCollisionCapsule(); var capsuleHeight = Vec3.distance(capsuleData.start, capsuleData.end); - var offset = Vec3.distance(Vec3.sum(capsuleData.start, {x: 0, y: 0.5*capsuleHeight, z: 0}), MyAvatar.position); - var radius = capsuleData.radius; - var height = 2.0 * radius + capsuleHeight; - var scale = height/2.0; - + var scale = capsuleData.scale; + var offset = Vec3.distance(Vec3.sum(capsuleData.start, {x: 0, y: 0.5*capsuleHeight, z: 0}), MyAvatar.position)/scale; + var radius = capsuleData.radius/scale; + var height = 2.0 * radius + capsuleHeight/scale; + _this.teleportHandCollisionPick = Picks.createPick(PickType.Collision, { enabled: true, parentID: Pointers.getPointerProperties(_this.teleportParabolaHandInvisible).renderStates["invisible"].end, @@ -232,7 +230,7 @@ Script.include("/~/system/libraries/controllers.js"); } }, position: { x: 0, y: offset + (height * 0.5), z: 0 }, - threshold: scale * _this.capsuleThreshold + threshold: _this.capsuleThreshold * scale }); _this.teleportHeadCollisionPick = Picks.createPick(PickType.Collision, { @@ -248,7 +246,7 @@ Script.include("/~/system/libraries/controllers.js"); } }, position: { x: 0, y: offset + (height * 0.5), z: 0 }, - threshold: scale * _this.capsuleThreshold + threshold: _this.capsuleThreshold * scale }); } @@ -368,15 +366,15 @@ Script.include("/~/system/libraries/controllers.js"); } else { result = Pointers.getPrevPickResult(_this.teleportParabolaHandVisible); } - teleportLocationType = getTeleportTargetType(result); + teleportLocationType = getTeleportTargetType(result, collisionResult); } if (teleportLocationType === TARGET.NONE) { // Use the cancel default state - this.setTeleportState(mode, "noend", ""); + this.setTeleportState(mode, "cancel", ""); } else if (teleportLocationType === TARGET.INVALID || teleportLocationType === TARGET.INVISIBLE) { - this.setTeleportState(mode, "", "noend"); - } else if (teleportLocationType === TARGET.CANCEL) { + this.setTeleportState(mode, "", "cancel"); + } else if (teleportLocationType === TARGET.COLLIDES) { this.setTeleportState(mode, "cancel", "invisible"); } else if (teleportLocationType === TARGET.SURFACE) { this.setTeleportState(mode, "teleport", "invisible"); @@ -496,6 +494,12 @@ Script.include("/~/system/libraries/controllers.js"); return TARGET.INVALID; } } + + if (collisionResult.collisionRegion != undefined) { + if (collisionResult.intersects) { + return TARGET.COLLIDES; + } + } if (!props.visible) { return TARGET.INVISIBLE; @@ -507,11 +511,6 @@ Script.include("/~/system/libraries/controllers.js"); if (angle > MAX_ANGLE_FROM_UP_TO_TELEPORT) { return TARGET.INVALID; } else { - if (collisionResult.collisionRegion != undefined) { - if (collisionResult.intersects) { - return TARGET.CANCEL; - } - } return TARGET.SURFACE; } } @@ -605,54 +604,11 @@ Script.include("/~/system/libraries/controllers.js"); } }; - // This class execute a function after the param value haven't been set for a certain time - // It's used in this case to recreate the collision picks once when the avatar scale process ends - - var AfterSet = function(time, maxsteps, callback) { - var self = this; - this.time = time; - this.callback = callback; - this.init = false; - this.value = 0; - this.interval; - this.maxsteps = maxsteps; - this.steps = 0; - this.restateValue = function(value) { - if (self.steps++ > self.maxsteps) { - self.callback.call(this, self.value); - self.steps = 0; - } - if (!self.init) { - console.log("Starting apply after"); - } - self.init = true; - self.value = value; - if (self.interval !== undefined) { - Script.clearInterval(self.interval); - } - self.interval = Script.setInterval(function() { - self.callback.call(this, self.value); - self.init = false; - Script.clearInterval(self.interval); - self.interval = undefined; - }, self.time); - } - } - - var afterSet = new AfterSet(100, 30, function(value) { - leftTeleporter.recreateCollisionPicks(); - rightTeleporter.recreateCollisionPicks(); - }); - MyAvatar.onLoadComplete.connect(function () { leftTeleporter.recreateCollisionPicks(); rightTeleporter.recreateCollisionPicks(); }); - MyAvatar.sensorToWorldScaleChanged.connect(function() { - afterSet.restateValue(MyAvatar.getSensorToWorldScale()); - }); - Messages.subscribe('Hifi-Teleport-Disabler'); Messages.subscribe('Hifi-Teleport-Ignore-Add'); Messages.subscribe('Hifi-Teleport-Ignore-Remove'); From 949efff43919c08e3993301493391b69c153f0ad Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Fri, 7 Sep 2018 13:21:17 -0700 Subject: [PATCH 376/744] Fix blendshapes playing back in recordings at 2x their proper values --- assignment-client/src/Agent.cpp | 10 ++++++++++ assignment-client/src/avatars/ScriptableAvatar.cpp | 12 ++++++++++++ assignment-client/src/avatars/ScriptableAvatar.h | 9 ++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 2f03f15da7..b75567e3cb 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -391,8 +391,18 @@ void Agent::executeScript() { if (recordingInterface->getPlayFromCurrentLocation()) { scriptedAvatar->setRecordingBasis(); } + + // these procedural movements are included in the recordings + scriptedAvatar->setHasProceduralEyeFaceMovement(false); + scriptedAvatar->setHasProceduralBlinkFaceMovement(false); + scriptedAvatar->setHasAudioEnabledFaceMovement(false); } else { scriptedAvatar->clearRecordingBasis(); + + // restore procedural blendshape movement + scriptedAvatar->setHasProceduralEyeFaceMovement(true); + scriptedAvatar->setHasProceduralBlinkFaceMovement(true); + scriptedAvatar->setHasAudioEnabledFaceMovement(true); } }); diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 6f04cfa196..7d2b267a05 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -145,3 +145,15 @@ void ScriptableAvatar::update(float deltatime) { _clientTraitsHandler->sendChangedTraitsToMixer(); } + +void ScriptableAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { + _headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement); +} + +void ScriptableAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) { + _headData->setHasProceduralEyeFaceMovement(hasProceduralEyeFaceMovement); +} + +void ScriptableAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { + _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); +} diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 89f9369133..52beba72a1 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -157,9 +157,16 @@ public: virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override; + void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); + bool getHasProceduralBlinkFaceMovement() const override { return _headData->getHasProceduralBlinkFaceMovement(); } + void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement); + bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); } + void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); + bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); } + private slots: void update(float deltatime); - + private: AnimationPointer _animation; AnimationDetails _animationDetails; From adac872a117bb76f6330c1980bde0e7b1b5a112f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 30 Aug 2018 15:10:05 -0700 Subject: [PATCH 377/744] clear other avatars in agent on avatar mixer disconnect --- interface/src/Application.cpp | 8 -------- interface/src/Application.h | 1 - interface/src/avatar/AvatarManager.h | 2 +- libraries/avatars/src/AvatarHashMap.cpp | 15 +++++++++++++++ libraries/avatars/src/AvatarHashMap.h | 2 ++ 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 40edcdbd76..4c4235a670 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1192,7 +1192,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); - connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &Application::clearDomainAvatars); connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() { getOverlays().deleteOverlay(getTabletScreenID()); getOverlays().deleteOverlay(getTabletHomeButtonID()); @@ -6368,10 +6367,6 @@ void Application::clearDomainOctreeDetails() { getMyAvatar()->setAvatarEntityDataChanged(true); } -void Application::clearDomainAvatars() { - DependencyManager::get()->clearOtherAvatars(); -} - void Application::domainURLChanged(QUrl domainURL) { // disable physics until we have enough information about our new location to not cause craziness. resetPhysicsReadyInformation(); @@ -6459,9 +6454,6 @@ void Application::nodeKilled(SharedNodePointer node) { } else if (node->getType() == NodeType::EntityServer) { // we lost an entity server, clear all of the domain octree details clearDomainOctreeDetails(); - } else if (node->getType() == NodeType::AvatarMixer) { - // our avatar mixer has gone away - clear the hash of avatars - DependencyManager::get()->clearOtherAvatars(); } else if (node->getType() == NodeType::AssetServer) { // asset server going away - check if we have the asset browser showing diff --git a/interface/src/Application.h b/interface/src/Application.h index aa8323cd38..f988795bc6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -437,7 +437,6 @@ private slots: void onDesktopRootContextCreated(QQmlContext* qmlContext); void showDesktop(); void clearDomainOctreeDetails(); - void clearDomainAvatars(); void onAboutToQuit(); void onPresent(quint32 frameCount); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index bcdfc064bd..7e1e1f4ff1 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -93,7 +93,7 @@ public: void postUpdate(float deltaTime, const render::ScenePointer& scene); - void clearOtherAvatars(); + void clearOtherAvatars() override; void deleteAllAvatars(); void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index c1246866dc..0c5ca06989 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -112,6 +112,12 @@ AvatarHashMap::AvatarHashMap() { packetReceiver.registerListener(PacketType::BulkAvatarTraits, this, "processBulkAvatarTraits"); connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); + + connect(nodeList.data(), &NodeList::nodeKilled, this, [this](SharedNodePointer killedNode){ + if (killedNode->getType() == NodeType::AvatarMixer) { + clearOtherAvatars(); + } + }); } QVector AvatarHashMap::getAvatarIdentifiers() { @@ -427,3 +433,12 @@ void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& ol emit avatarSessionChangedEvent(sessionUUID, oldUUID); } +void AvatarHashMap::clearOtherAvatars() { + QWriteLocker locker(&_hashLock); + + AvatarHash::iterator avatarIterator = _avatarHash.begin(); + while (avatarIterator != _avatarHash.end()) { + handleRemovedAvatar(*avatarIterator); + avatarIterator = _avatarHash.erase(avatarIterator); + } +} diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 0f847b2a61..70d7f8c04d 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -101,6 +101,8 @@ public: void setReplicaCount(int count); int getReplicaCount() { return _replicas.getReplicaCount(); }; + virtual void clearOtherAvatars(); + signals: /**jsdoc From 9350d6f36e5c82b142619f5ac3e8665d7802e980 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Fri, 7 Sep 2018 15:35:10 -0700 Subject: [PATCH 378/744] removing performance counters --- libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp | 1 - libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp | 5 +---- libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp | 3 --- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp index f326ef39b9..8d46b4c6e3 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp @@ -259,7 +259,6 @@ void GLBackend::updateInput() { // Profile the count of buffers to update and use it to short cut the for loop int numInvalids = (int) _input._invalidBuffers.count(); _stats._ISNumInputBufferChanges += numInvalids; - PROFILE_COUNTER_IF_CHANGED(render_gpu, "numInputBuffersbound", int, numInvalids); for (GLuint buffer = 0; buffer < _input._buffers.size(); buffer++, vbo++, offset++, stride++) { if (_input._invalidBuffers.test(buffer)) { diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp index cedb18c87a..c61ffb09e5 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp @@ -80,10 +80,7 @@ void GL41Backend::updateInput() { auto& inputChannels = _input._format->getChannels(); int numInvalids = (int)_input._invalidBuffers.count(); _stats._ISNumInputBufferChanges += numInvalids; - - // Profile the count of buffers to update - PROFILE_COUNTER_IF_CHANGED(render_gpu, "numInputBuffersbound", int, numInvalids); - + GLuint boundVBO = 0; for (auto& channelIt : inputChannels) { const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp index 4a8bdc43f3..7cd8756ead 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp @@ -36,7 +36,6 @@ void GL45Backend::updateInput() { _input._lastUpdateStereoState = isStereoNow; if (_input._invalidFormat) { - PROFILE_RANGE(render_gpu, "bindInputFormat"); InputStageState::ActivationCache newActivation; // Assign the vertex format required @@ -136,7 +135,6 @@ void GL45Backend::updateInput() { // Profile the count of buffers to update and use it to short cut the for loop int numInvalids = (int) _input._invalidBuffers.count(); _stats._ISNumInputBufferChanges += numInvalids; - PROFILE_COUNTER_IF_CHANGED(render_gpu, "numInputBuffersbound", int, numInvalids); auto numBuffers = _input._buffers.size(); for (GLuint buffer = 0; buffer < numBuffers; buffer++, vbo++, offset++, stride++) { @@ -149,7 +147,6 @@ void GL45Backend::updateInput() { } } - _input._invalidBuffers.reset(); (void)CHECK_GL_ERROR(); } From c2fe2b60b3ee8e04b2b122f903de1b05591185f0 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 7 Sep 2018 15:49:47 -0700 Subject: [PATCH 379/744] Use std::vector for ignored avatars since the list will be small --- .../src/avatars/AvatarMixerClientData.cpp | 16 +++++++++++++++- .../src/avatars/AvatarMixerClientData.h | 8 ++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index c54db90f5d..6c01e6e02b 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -11,6 +11,7 @@ #include "AvatarMixerClientData.h" +#include #include #include @@ -239,8 +240,21 @@ void AvatarMixerClientData::ignoreOther(const Node* self, const Node* other) { } } +bool AvatarMixerClientData::isRadiusIgnoring(const QUuid& other) const { + return std::find(_radiusIgnoredOthers.cbegin(), _radiusIgnoredOthers.cend(), other) != _radiusIgnoredOthers.cend(); +} + +void AvatarMixerClientData::addToRadiusIgnoringSet(const QUuid& other) { + if (!isRadiusIgnoring(other)) { + _radiusIgnoredOthers.push_back(other); + } +} + void AvatarMixerClientData::removeFromRadiusIgnoringSet(const QUuid& other) { - _radiusIgnoredOthers.erase(other); + auto ignoredOtherIter = std::find(_radiusIgnoredOthers.cbegin(), _radiusIgnoredOthers.cend(), other); + if (ignoredOtherIter != _radiusIgnoredOthers.cend()) { + _radiusIgnoredOthers.erase(ignoredOtherIter); + } } void AvatarMixerClientData::resetSentTraitData(Node::LocalID nodeLocalID) { diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 64b1ea3edf..d38a90ef1f 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include @@ -91,8 +91,8 @@ public: void loadJSONStats(QJsonObject& jsonObject) const; glm::vec3 getPosition() const { return _avatar ? _avatar->getWorldPosition() : glm::vec3(0); } - bool isRadiusIgnoring(const QUuid& other) const { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); } - void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); } + bool isRadiusIgnoring(const QUuid& other) const; + void addToRadiusIgnoringSet(const QUuid& other); void removeFromRadiusIgnoringSet(const QUuid& other); void ignoreOther(SharedNodePointer self, SharedNodePointer other); void ignoreOther(const Node* self, const Node* other); @@ -167,7 +167,7 @@ private: int _numOutOfOrderSends = 0; SimpleMovingAverage _avgOtherAvatarDataRate; - std::unordered_set _radiusIgnoredOthers; + std::vector _radiusIgnoredOthers; ConicalViewFrustums _currentViewFrustums; int _recentOtherAvatarsInView { 0 }; From 5140999445ffeb87a39e926d50e6c0b2e43df9a9 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Fri, 7 Sep 2018 15:57:04 -0700 Subject: [PATCH 380/744] Forcing all the meshes with 3 buffers aka the blendshape layout --- libraries/fbx/src/FBXReader_Mesh.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index 72f0fc6f23..e8b5abcc9c 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -761,6 +761,12 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { bool interleavePositions = true; bool interleaveNormalsTangents = true; + // TODO: We are using the same vertex format layout for all meshes because this is more efficient + // This work is going into rc73 release which is meant to be used for the SPot500 event and we are picking the format + // that works best for blendshaped and skinned meshes aka the avatars. + // We will improve this technique in a hot fix to 73. + hasBlendShapes = true; + // If has blend shapes allocate and assign buffers for pos and tangents now if (hasBlendShapes) { auto posBuffer = std::make_shared(); From c0a153482a63689543821d1fbf142d207c99de8e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 8 Sep 2018 11:55:47 +1200 Subject: [PATCH 381/744] Update mini tablet model --- .../system/assets/models/miniTabletBlank.fbx | Bin 0 -> 202832 bytes scripts/system/assets/models/tinyTablet.fbx | Bin 476000 -> 0 bytes scripts/system/html/css/miniTablet.css | 2 +- scripts/system/miniTablet.js | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 scripts/system/assets/models/miniTabletBlank.fbx delete mode 100644 scripts/system/assets/models/tinyTablet.fbx diff --git a/scripts/system/assets/models/miniTabletBlank.fbx b/scripts/system/assets/models/miniTabletBlank.fbx new file mode 100644 index 0000000000000000000000000000000000000000..a2faa2a80acd99d0a3bbc8e3d0e672b4352d29ad GIT binary patch literal 202832 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

I_FgN>_`ysz4r)5pEO>a}Qw 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

- +
diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 8436e36087..0893108109 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -32,7 +32,6 @@ tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"), DEBUG = false; - // #region Utilities ======================================================================================================= function debug(message) { if (!DEBUG) { @@ -75,9 +74,6 @@ return hand === LEFT_HAND ? RIGHT_HAND : LEFT_HAND; } - // #endregion - - // #region UI ============================================================================================================== UI = function () { @@ -511,9 +507,6 @@ }; - // #endregion - - // #region State Machine =================================================================================================== State = function () { @@ -535,8 +528,6 @@ STATE_MACHINE, miniState = MINI_DISABLED, miniHand, - updateTimer = null, - UPDATE_INTERVAL = 25, // Mini tablet scaling. MINI_SCALE_DURATION = 250, @@ -572,10 +563,7 @@ function enterMiniDisabled() { // Stop updates. - if (updateTimer !== null) { - Script.clearTimeout(updateTimer); - updateTimer = null; - } + Script.update.disconnect(updateState); // Stop monitoring mute changes. Audio.mutedChanged.disconnect(ui.updateMutedStatus); @@ -593,7 +581,7 @@ Audio.mutedChanged.connect(ui.updateMutedStatus); // Start updates. - updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL); + Script.update.connect(updateState); } function shouldShowMini(hand) { @@ -943,7 +931,6 @@ if (STATE_MACHINE[STATE_STRINGS[miniState]].update) { STATE_MACHINE[STATE_STRINGS[miniState]].update(); } - updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL); } function create() { @@ -966,7 +953,6 @@ MINI_VISIBLE: MINI_VISIBLE, MINI_EXPANDING: MINI_EXPANDING, TABLET_OPEN: TABLET_OPEN, - updateState: updateState, setState: setState, getState: getState, getHand: getHand, @@ -974,9 +960,6 @@ }; }; - // #endregion - - // #region External Events ================================================================================================= function onMessageReceived(channel, data, senderID, localOnly) { var message, @@ -1021,9 +1004,6 @@ } } - // #endregion - - // #region Set-up and tear-down ============================================================================================ function setUp() { miniState = new State(); @@ -1054,6 +1034,4 @@ setUp(); Script.scriptEnding.connect(tearDown); - // #endregion - }()); From 1088a0f9ae26437af880b702b0b14a93b2985fa5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 28 Sep 2018 10:19:17 +1200 Subject: [PATCH 710/744] Fix invalid state transition in desktop mode --- scripts/system/miniTablet.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 0893108109..395b2cb71f 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -980,7 +980,7 @@ return; } - if (message.action === "grab" && message.grabbedEntity === HMD.tabletID) { + if (message.action === "grab" && message.grabbedEntity === HMD.tabletID && HMD.active) { // Tablet may have been grabbed after it replaced expanded mini tablet. miniState.setState(miniState.MINI_HIDDEN); } else if (message.action === "grab" && miniState.getState() === miniState.MINI_VISIBLE) { @@ -992,7 +992,9 @@ function onWentAway() { // Mini tablet only available when user is not away. - miniState.setState(miniState.MINI_HIDDEN); + if (HMD.active) { + miniState.setState(miniState.MINI_HIDDEN); + } } function onDisplayModeChanged() { From 5fc24b258765dc83ae4fe73916c333cfa8c7c70c Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 27 Sep 2018 19:20:38 -0300 Subject: [PATCH 711/744] UI fixes in login and signup for S8 and S9 aspect ratio --- android/app/src/main/res/layout/fragment_login.xml | 9 ++++----- android/app/src/main/res/layout/fragment_signup.xml | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/android/app/src/main/res/layout/fragment_login.xml b/android/app/src/main/res/layout/fragment_login.xml index e5a16b453f..e9b2577ee7 100644 --- a/android/app/src/main/res/layout/fragment_login.xml +++ b/android/app/src/main/res/layout/fragment_login.xml @@ -97,8 +97,6 @@ android:background="@drawable/rounded_button" android:fontFamily="@font/raleway_semibold" android:paddingBottom="0dp" - android:paddingLeft="25dp" - android:paddingRight="25dp" android:paddingTop="0dp" android:text="@string/login" android:textColor="@color/white_opaque" @@ -123,20 +121,21 @@

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 literal 0 HcmV?d00001 diff --git a/interface/resources/meshes/redirect/oopsDialog_vague.fbx b/interface/resources/meshes/redirect/oopsDialog_vague.fbx new file mode 100644 index 0000000000000000000000000000000000000000..324d90578b833552f01a1199fbded078b49900ba GIT binary patch literal 32480 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 Wum=D0kPhhoqiij2TGU*FCj1{yz#Z8D literal 0 HcmV?d00001 diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index f983a7ffef..ec172561af 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -625,7 +625,7 @@ "lastEdited": 1536108160862286, "lastEditedBy": "{4656d4a8-5e61-4230-ab34-2888d7945bd6}", "locked": true, - "modelURL": "http://hifi-content.s3.amazonaws.com/wayne/models/oopsDialog_protocol.fbx", + "modelURL": Script.resourcesPath() + "meshes/redirect/oopsDialog_protocol.fbx", "name": "Oops Dialog", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { @@ -648,7 +648,7 @@ "y": 0.4957197904586792, "z": -7.62939453125e-05 }, - "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/oopsEntityScript.js", + "script": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/scripts/system/oopsEntityScript.js", "scriptTimestamp": 1536102551825, "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" diff --git a/scripts/system/oopsEntityScript.js b/scripts/system/oopsEntityScript.js new file mode 100644 index 0000000000..bf10fbb9dd --- /dev/null +++ b/scripts/system/oopsEntityScript.js @@ -0,0 +1,41 @@ +(function() { + var PROTOCOL_MISMATCH = 1; + var NOT_AUTHORIZED = 3; + + this.entityID = Uuid.NULL; + this.preload = function(entityID) { + this.entityID = entityID; + }; + function pingError() { + if(this.entityID === undefined) { + var entities = Entities.findEntitiesByName("Oops Dialog", MyAvatar.position, 10); + if(entities.length > 0) { + this.entityID = entities[0]; + } + } + var error = location.lastDomainConnectionError; + var newModel = ""; + var hostedSite = Script.resourcesPath() + "meshes/redirect"; + if (error === PROTOCOL_MISMATCH) { + newModel = "oopsDialog_protocol"; + } else if (error === NOT_AUTHORIZED) { + newModel = "oopsDialog_auth"; + } else { + newModel = "oopsDialog_vague"; + } + var props = Entities.getEntityProperties(this.entityID, ["modelURL"]); + var newModelURL = hostedSite + newModel + ".fbx"; + if(props.modelURL !== newModelURL) { + var newFileURL = newModelURL + "/" + newModel + ".png"; + var newTextures = {"file16": newFileURL, "file17": newFileURL}; + Entities.editEntity(this.entityID, {modelURL: newModelURL, originalTextures: JSON.stringify(newTextures)}); + } + }; + var ping = Script.setInterval(pingError, 5000); + + function cleanup() { + Script.clearInterval(ping); + }; + + Script.scriptEnding.connect(cleanup); +}); From 8b6c21e0bbd94f4e0b1ed66aca3188aee8d58d23 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 4 Sep 2018 22:19:12 -0700 Subject: [PATCH 242/744] updating script path TODO: will fix with s3 hosted script --- interface/resources/serverless/redirect.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index ec172561af..00bbe17691 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -648,7 +648,7 @@ "y": 0.4957197904586792, "z": -7.62939453125e-05 }, - "script": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/scripts/system/oopsEntityScript.js", + "script": "https://raw.githubusercontent.com/wayne-chen/hifi/interstitalMerged/scripts/system/oopsEntityScript.js", "scriptTimestamp": 1536102551825, "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" From afd7321e7f81a19f48b797887849cc7ebb7e9612 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 4 Sep 2018 23:16:54 -0700 Subject: [PATCH 243/744] updating redirect with proper fbx TODO: fix path when uploaded the fbx files --- interface/resources/serverless/redirect.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index ec172561af..17799b952d 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -625,7 +625,7 @@ "lastEdited": 1536108160862286, "lastEditedBy": "{4656d4a8-5e61-4230-ab34-2888d7945bd6}", "locked": true, - "modelURL": Script.resourcesPath() + "meshes/redirect/oopsDialog_protocol.fbx", + "modelURL": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/interface/resources/meshes/redirect/oopsDialog_protocol.fbx", "name": "Oops Dialog", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { From d6442b350c4da1110db0111a6d3283b593f5cc80 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 4 Sep 2018 23:18:52 -0700 Subject: [PATCH 244/744] updating redirect to the interstitial page url TODO: Fix this link to s3 bucket hosted file --- interface/resources/serverless/redirect.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index 511c1c410c..539e92203b 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -625,7 +625,7 @@ "lastEdited": 1536108160862286, "lastEditedBy": "{4656d4a8-5e61-4230-ab34-2888d7945bd6}", "locked": true, - "modelURL": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/interface/resources/meshes/redirect/oopsDialog_protocol.fbx", + "modelURL": "https://raw.githubusercontent.com/wayne-chen/hifi/interstitalMerged/interface/resources/meshes/redirect/oopsDialog_protocol.fbx", "name": "Oops Dialog", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { From f38a26a1ffd0e95aeffa229ae4fc9a08be98cfbe Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 5 Sep 2018 09:04:17 -0700 Subject: [PATCH 245/744] updating location of oops dialog script --- interface/resources/serverless/redirect.json | 4 +- scripts/system/oopsEntityScript.js | 41 -------------------- 2 files changed, 2 insertions(+), 43 deletions(-) delete mode 100644 scripts/system/oopsEntityScript.js diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index 17799b952d..1bb2aa80c7 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -625,7 +625,7 @@ "lastEdited": 1536108160862286, "lastEditedBy": "{4656d4a8-5e61-4230-ab34-2888d7945bd6}", "locked": true, - "modelURL": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/interface/resources/meshes/redirect/oopsDialog_protocol.fbx", + "modelURL": "", "name": "Oops Dialog", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { @@ -648,7 +648,7 @@ "y": 0.4957197904586792, "z": -7.62939453125e-05 }, - "script": "https://raw.githubusercontent.com/wayne-chen/hifi/404DomainRedirect/scripts/system/oopsEntityScript.js", + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/oopsEntityScript.js", "scriptTimestamp": 1536102551825, "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" diff --git a/scripts/system/oopsEntityScript.js b/scripts/system/oopsEntityScript.js deleted file mode 100644 index bf10fbb9dd..0000000000 --- a/scripts/system/oopsEntityScript.js +++ /dev/null @@ -1,41 +0,0 @@ -(function() { - var PROTOCOL_MISMATCH = 1; - var NOT_AUTHORIZED = 3; - - this.entityID = Uuid.NULL; - this.preload = function(entityID) { - this.entityID = entityID; - }; - function pingError() { - if(this.entityID === undefined) { - var entities = Entities.findEntitiesByName("Oops Dialog", MyAvatar.position, 10); - if(entities.length > 0) { - this.entityID = entities[0]; - } - } - var error = location.lastDomainConnectionError; - var newModel = ""; - var hostedSite = Script.resourcesPath() + "meshes/redirect"; - if (error === PROTOCOL_MISMATCH) { - newModel = "oopsDialog_protocol"; - } else if (error === NOT_AUTHORIZED) { - newModel = "oopsDialog_auth"; - } else { - newModel = "oopsDialog_vague"; - } - var props = Entities.getEntityProperties(this.entityID, ["modelURL"]); - var newModelURL = hostedSite + newModel + ".fbx"; - if(props.modelURL !== newModelURL) { - var newFileURL = newModelURL + "/" + newModel + ".png"; - var newTextures = {"file16": newFileURL, "file17": newFileURL}; - Entities.editEntity(this.entityID, {modelURL: newModelURL, originalTextures: JSON.stringify(newTextures)}); - } - }; - var ping = Script.setInterval(pingError, 5000); - - function cleanup() { - Script.clearInterval(ping); - }; - - Script.scriptEnding.connect(cleanup); -}); From 179138aa106895efafdf6a26469a31061946a9a8 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 5 Sep 2018 10:36:06 -0700 Subject: [PATCH 246/744] updating serverless file to have dynamic oops --- interface/resources/serverless/redirect.json | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index 1bb2aa80c7..48b1a7078f 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -624,7 +624,6 @@ "id": "{dfe92dce-f09d-4e9e-b3ed-c68ecd4d476f}", "lastEdited": 1536108160862286, "lastEditedBy": "{4656d4a8-5e61-4230-ab34-2888d7945bd6}", - "locked": true, "modelURL": "", "name": "Oops Dialog", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", From c6229c5c604731e3154d31455e5f5fe46b6242ff Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 5 Sep 2018 10:37:45 -0700 Subject: [PATCH 247/744] updating serverless file to have dynamic oops --- interface/resources/serverless/redirect.json | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index 1bb2aa80c7..48b1a7078f 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -624,7 +624,6 @@ "id": "{dfe92dce-f09d-4e9e-b3ed-c68ecd4d476f}", "lastEdited": 1536108160862286, "lastEditedBy": "{4656d4a8-5e61-4230-ab34-2888d7945bd6}", - "locked": true, "modelURL": "", "name": "Oops Dialog", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", From 1bea6c9ea38f223d730f409542bb965d43858b93 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 5 Sep 2018 10:38:12 -0700 Subject: [PATCH 248/744] if not already logged in, show login dialog at launch-time rather than connect-to-domain-time --- interface/src/Application.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 67dbcf355f..a2f5033622 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2299,6 +2299,19 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground); AndroidHelper::instance().notifyLoadComplete(); #endif + + static int CHECK_LOGIN_TIMER = 3000; + QTimer* checkLoginTimer = new QTimer(this); + checkLoginTimer->setInterval(CHECK_LOGIN_TIMER); + checkLoginTimer->setSingleShot(true); + connect(checkLoginTimer, &QTimer::timeout, this, []() { + auto accountManager = DependencyManager::get(); + auto dialogsManager = DependencyManager::get(); + if (!accountManager->isLoggedIn()) { + dialogsManager->showLoginDialog(); + } + }); + checkLoginTimer->start(); } void Application::updateVerboseLogging() { From f7e84995b41961d08ac6ecdcffef170c8b6c3253 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 5 Sep 2018 10:58:37 -0700 Subject: [PATCH 249/744] Only build the sortable vector once, now we're using vectors for the priority sort --- .../src/avatars/AvatarMixerSlave.cpp | 60 ++++++------------- 1 file changed, 18 insertions(+), 42 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 81ee9c18f8..a61f65ffb0 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -288,35 +288,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // Quadruple the scale of first bounding box nodeBox.embiggen(4.0f); - - // setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes - struct AvatarSortData { - AvatarSortData(const Node* node, AvatarData* avatarData, quint64 lastEncodeTime) - : _node(node) - , _avatarData(avatarData) - , _lastEncodeTime(lastEncodeTime) - { } - const Node* _node; - AvatarData* _avatarData; - quint64 _lastEncodeTime; - }; - // Temporary info about the avatars we're sending: - std::vector avatarsToSort; - avatarsToSort.reserve(_end - _begin); - std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { - Node* otherNodeRaw = otherNode.data(); - // make sure this is an agent that we have avatar data for before considering it for inclusion - if (otherNodeRaw->getType() == NodeType::Agent - && otherNodeRaw->getLinkedData()) { - const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNodeRaw->getLinkedData()); - - - AvatarData* otherAvatar = otherNodeData->getAvatarSharedPointer().get(); - auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(otherAvatar->getSessionUUID()); - avatarsToSort.emplace_back(AvatarSortData(otherNodeRaw, otherAvatar, lastEncodeTime)); - } - }); - class SortableAvatar: public PrioritySortUtil::Sortable { public: SortableAvatar() = delete; @@ -345,16 +316,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge); - sortedAvatars.reserve(avatarsToSort.size()); + sortedAvatars.reserve(_end - _begin); - // ignore or sort - for (const auto& avatar : avatarsToSort) { - auto avatarNode = avatar._node; - if (avatarNode == destinationNode) { - // don't echo updates to self + for (auto listedNode = _begin; listedNode != _end; ++listedNode) { + Node* otherNodeRaw = (*listedNode).data(); + if (otherNodeRaw->getType() != NodeType::Agent + || !otherNodeRaw->getLinkedData() + || otherNodeRaw == destinationNode) { continue; } + auto avatarNode = otherNodeRaw; + bool shouldIgnore = false; // We ignore other nodes for a couple of reasons: // 1) ignore bubbles and ignore specific node @@ -364,8 +337,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map - const AvatarMixerClientData* avatarNodeData = reinterpret_cast(avatarNode->getLinkedData()); - assert(avatarNodeData); // we can't have gotten here without avatarNode having valid data + const AvatarMixerClientData* avatarClientNodeData = reinterpret_cast(avatarNode->getLinkedData()); + assert(avatarClientNodeData); // we can't have gotten here without avatarNode having valid data quint64 startIgnoreCalculation = usecTimestampNow(); // make sure we have data for this avatar, that it isn't the same node, @@ -378,11 +351,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // Check to see if the space bubble is enabled // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored if (destinationNode->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { - float sensorToWorldScale = avatarNodeData->getAvatarSharedPointer()->getSensorToWorldScale(); + float sensorToWorldScale = avatarClientNodeData->getAvatarSharedPointer()->getSensorToWorldScale(); // Define the scale of the box for the current other node - glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f * sensorToWorldScale; + glm::vec3 otherNodeBoxScale = (avatarClientNodeData->getPosition() - avatarClientNodeData->getGlobalBoundingBoxCorner()) * 2.0f * sensorToWorldScale; // Set up the bounding box for the current other node - AABox otherNodeBox(avatarNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); + AABox otherNodeBox(avatarClientNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); // Clamp the size of the bounding box to a minimum scale if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { otherNodeBox.setScaleStayCentered(minBubbleSize); @@ -405,7 +378,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (!shouldIgnore) { AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID()); - AvatarDataSequenceNumber lastSeqFromSender = avatarNodeData->getLastReceivedSequenceNumber(); + AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber(); // FIXME - This code does appear to be working. But it seems brittle. // It supports determining if the frame of data for this "other" @@ -430,7 +403,10 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (!shouldIgnore) { // sort this one for later - sortedAvatars.push(SortableAvatar(avatar._avatarData, avatar._node, avatar._lastEncodeTime)); + const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData(); + auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNodeData->getSessionUUID()); + + sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); } } From 8344cb9affebbf01e4da273817d00c64869a4b83 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Wed, 5 Sep 2018 11:07:04 -0700 Subject: [PATCH 250/744] REfactor the FBXReader_Mesh for different vertex formats --- libraries/fbx/src/FBXReader_Mesh.cpp | 154 ++++++++++++++++++--------- 1 file changed, 104 insertions(+), 50 deletions(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index 4ecca2b234..ce80dfb592 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -745,66 +745,116 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // Now we decide on how to interleave the attributes and provide the vertices among bufers: - // Aka the Vertex format - auto vf = std::make_shared(); - gpu::Offset buf0Offset = 0; - vf->setAttribute(gpu::Stream::POSITION, 0, positionElement); - buf0Offset += positionElement.getSize(); + // Aka the Vertex format and the vertexBufferStream + auto vertexFormat = std::make_shared(); + auto vertexBufferStream = std::make_shared(); - gpu::Offset buf1Offset = 0; - if (normalsSize) { - vf->setAttribute(gpu::Stream::NORMAL, 1, normalElement, buf1Offset); - buf1Offset = normalElement.getSize(); - vf->setAttribute(gpu::Stream::TANGENT, 1, normalElement, buf1Offset); - buf1Offset = normalElement.getSize(); + // Decision time: + // if blendshapes then keep position and normals/tangents as separated channel buffers from interleaved attributes + // else everything is interleaved in one buffer + + // Default case is no blend shapes + gpu::BufferPointer attribBuffer; + int totalAttribBufferSize = totalVertsSize; + gpu::uint8 posChannel = 0; + gpu::uint8 tangentChannel = posChannel; + gpu::uint8 attribChannel = posChannel; + bool interleavePositions = true; + bool interleaveNormalsTangents = true; + + // If has blend shapes allocate and assign buffers for pos and tangents now + if (hasBlendShapes) { + + auto posBuffer = std::make_shared(); + posBuffer->setData(positionsSize, (const gpu::Byte*) vertBuffer->getData() + positionsOffset); + vertexBufferStream->addBuffer(posBuffer, 0, positionElement.getSize()); + + auto tangentBuffer = std::make_shared(); + tangentBuffer->setData(normalsAndTangentsSize, (const gpu::Byte*) vertBuffer->getData() + normalsAndTangentsOffset); + vertexBufferStream->addBuffer(tangentBuffer, 0, normalsAndTangentsStride); + + // update channels and attribBuffer size accordingly + interleavePositions = false; + interleaveNormalsTangents = false; + + tangentChannel = 1; + attribChannel = 2; + + totalAttribBufferSize = totalVertsSize - positionsSize - normalsAndTangentsSize; + } else { +/* + auto posBuffer = std::make_shared(); + posBuffer->setData(positionsSize, (const gpu::Byte*) vertBuffer->getData() + positionsOffset); + vertexBufferStream->addBuffer(posBuffer, 0, positionElement.getSize()); + + // update channels and attribBuffer size accordingly + interleavePositions = false; + + auto tangentBuffer = std::make_shared(); + tangentBuffer->setData(normalsAndTangentsSize, (const gpu::Byte*) vertBuffer->getData() + normalsAndTangentsOffset); + vertexBufferStream->addBuffer(tangentBuffer, 0, normalsAndTangentsStride); + + interleaveNormalsTangents = false; + + tangentChannel = 1; + attribChannel = 2; + + totalAttribBufferSize = totalVertsSize - positionsSize - normalsAndTangentsSize;*/ } - gpu::Offset buf2Offset = (0); + // Define the vertex format, compute the offset for each attributes as we append them to the vertex format + gpu::Offset bufOffset = 0; + if (positionsSize) { + vertexFormat->setAttribute(gpu::Stream::POSITION, posChannel, positionElement, bufOffset); + bufOffset += positionElement.getSize(); + if (!interleavePositions) { + bufOffset = 0; + } + } + if (normalsSize) { + vertexFormat->setAttribute(gpu::Stream::NORMAL, tangentChannel, normalElement, bufOffset); + bufOffset = normalElement.getSize(); + vertexFormat->setAttribute(gpu::Stream::TANGENT, tangentChannel, normalElement, bufOffset); + bufOffset = normalElement.getSize(); + if (!interleaveNormalsTangents) { + bufOffset = 0; + } + } + + // Pack normal and Tangent with the rest of atributes if no blend shapes if (colorsSize) { - vf->setAttribute(gpu::Stream::COLOR, 2, colorElement, buf2Offset); - buf2Offset += colorElement.getSize(); + vertexFormat->setAttribute(gpu::Stream::COLOR, attribChannel, colorElement, bufOffset); + bufOffset += colorElement.getSize(); } if (texCoordsSize) { - vf->setAttribute(gpu::Stream::TEXCOORD, 2, texCoordsElement, buf2Offset); - buf2Offset += texCoordsElement.getSize(); + vertexFormat->setAttribute(gpu::Stream::TEXCOORD, attribChannel, texCoordsElement, bufOffset); + bufOffset += texCoordsElement.getSize(); } if (texCoords1Size) { - vf->setAttribute(gpu::Stream::TEXCOORD1, 2, texCoordsElement, buf2Offset); - buf2Offset += texCoordsElement.getSize(); + vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, attribChannel, texCoordsElement, bufOffset); + bufOffset += texCoordsElement.getSize(); } else if (texCoordsSize) { - vf->setAttribute(gpu::Stream::TEXCOORD1, 2, texCoordsElement, buf2Offset - texCoordsElement.getSize()); + vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, attribChannel, texCoordsElement, bufOffset - texCoordsElement.getSize()); } if (clusterIndicesSize) { - vf->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, 2, clusterIndiceElement, buf2Offset); - buf2Offset += clusterIndiceElement.getSize(); + vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, attribChannel, clusterIndiceElement, bufOffset); + bufOffset += clusterIndiceElement.getSize(); } if (clusterWeightsSize) { - vf->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, 2, clusterWeightElement, buf2Offset); - buf2Offset += clusterWeightElement.getSize(); + vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, attribChannel, clusterWeightElement, bufOffset); + bufOffset += clusterWeightElement.getSize(); } - auto vbs = std::make_shared(); - - - auto vb = std::make_shared(); - vb->setData(extractedMesh.vertices.size() * sizeof(glm::vec3), - (const gpu::Byte*) extractedMesh.vertices.data()); - gpu::BufferView vbv(vb, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - vbs->addBuffer(vb, 0, buf0Offset); - - auto attribNTBuffer = std::make_shared(); - attribNTBuffer->resize(totalNTSize); - - auto attribBuffer = std::make_shared(); - attribBuffer->resize(totalAttributeSize); - - - + // Finally, allocate and fill the attribBuffer interleaving the attributes as needed: { - vbs->addBuffer(attribNTBuffer, 0, buf1Offset); + auto vPositionOffset = 0; + auto vPositionSize = (interleavePositions ? positionsSize / numVerts : 0); - auto vColorOffset = 0; + auto vNormalsAndTangentsOffset = vPositionOffset + vPositionSize; + auto vNormalsAndTangentsSize = (interleaveNormalsTangents ? normalsAndTangentsSize / numVerts : 0); + + auto vColorOffset = vNormalsAndTangentsOffset + vNormalsAndTangentsSize; auto vColorSize = colorsSize / numVerts; auto vTexcoord0Offset = vColorOffset + vColorSize; @@ -820,16 +870,17 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { auto vClusterWeightSize = clusterWeightsSize / numVerts; auto vStride = vClusterWeightOffset + vClusterWeightSize; - //int vStride = buf2Offset; + std::vector dest; - dest.resize(totalAttributeSize); + dest.resize(totalAttribBufferSize); auto vDest = dest.data(); - auto source = attribBuffer->getData(); - + auto source = vertBuffer->getData(); for (int i = 0; i < numVerts; i++) { + if (vPositionSize) memcpy(vDest + vPositionOffset, source + positionsOffset + i * vPositionSize, vPositionSize); + if (vNormalsAndTangentsSize) memcpy(vDest + vNormalsAndTangentsOffset, source + normalsAndTangentsOffset + i * vNormalsAndTangentsSize, vNormalsAndTangentsSize); if (vColorSize) memcpy(vDest + vColorOffset, source + colorsOffset + i * vColorSize, vColorSize); if (vTexcoord0Size) memcpy(vDest + vTexcoord0Offset, source + texCoordsOffset + i * vTexcoord0Size, vTexcoord0Size); if (vTexcoord1Size) memcpy(vDest + vTexcoord1Offset, source + texCoords1Offset + i * vTexcoord1Size, vTexcoord1Size); @@ -839,12 +890,15 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { vDest += vStride; } - attribBuffer->setData(totalAttributeSize, dest.data()); - - vbs->addBuffer(attribBuffer, 0, vStride); + auto attribBuffer = std::make_shared(); + attribBuffer->setData(totalAttribBufferSize, dest.data()); + vertexBufferStream->addBuffer(attribBuffer, 0, vStride); } - mesh->setVertexFormatAndStream(vf, vbs); + // MEsh vertex format and vertex stream is ready + mesh->setVertexFormatAndStream(vertexFormat, vertexBufferStream); + + // Index and Part Buffers unsigned int totalIndices = 0; foreach(const FBXMeshPart& part, extractedMesh.parts) { totalIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); From 198ec294acd8ee96596a833f64388db7535e5779 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 5 Sep 2018 20:13:48 +0200 Subject: [PATCH 251/744] restore window parent on presentationMode switch --- libraries/ui/src/InteractiveWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/ui/src/InteractiveWindow.cpp b/libraries/ui/src/InteractiveWindow.cpp index b4d5a068eb..6c7f2d503f 100644 --- a/libraries/ui/src/InteractiveWindow.cpp +++ b/libraries/ui/src/InteractiveWindow.cpp @@ -97,6 +97,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap #ifdef Q_OS_WIN connect(object, SIGNAL(nativeWindowChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); + connect(object, SIGNAL(presentationModeChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); #endif QUrl sourceURL{ sourceUrl }; From ce86e94e6f7b8c41fe5623d69b68eb1b66ed73ed Mon Sep 17 00:00:00 2001 From: sam gateau Date: Wed, 5 Sep 2018 11:34:33 -0700 Subject: [PATCH 252/744] Add simple optim and instrumentation for tthe number of input buffer changes per frame" --- libraries/fbx/src/FBXReader_Mesh.cpp | 25 +++---------------- .../src/gpu/gl/GLBackendInput.cpp | 9 +++++++ .../gpu-gl/src/gpu/gl41/GL41BackendInput.cpp | 5 +++- .../gpu-gl/src/gpu/gl45/GL45BackendInput.cpp | 15 ++++++----- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index ce80dfb592..ef2fcc23b4 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -781,25 +781,6 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { attribChannel = 2; totalAttribBufferSize = totalVertsSize - positionsSize - normalsAndTangentsSize; - } else { -/* - auto posBuffer = std::make_shared(); - posBuffer->setData(positionsSize, (const gpu::Byte*) vertBuffer->getData() + positionsOffset); - vertexBufferStream->addBuffer(posBuffer, 0, positionElement.getSize()); - - // update channels and attribBuffer size accordingly - interleavePositions = false; - - auto tangentBuffer = std::make_shared(); - tangentBuffer->setData(normalsAndTangentsSize, (const gpu::Byte*) vertBuffer->getData() + normalsAndTangentsOffset); - vertexBufferStream->addBuffer(tangentBuffer, 0, normalsAndTangentsStride); - - interleaveNormalsTangents = false; - - tangentChannel = 1; - attribChannel = 2; - - totalAttribBufferSize = totalVertsSize - positionsSize - normalsAndTangentsSize;*/ } // Define the vertex format, compute the offset for each attributes as we append them to the vertex format @@ -813,9 +794,9 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { } if (normalsSize) { vertexFormat->setAttribute(gpu::Stream::NORMAL, tangentChannel, normalElement, bufOffset); - bufOffset = normalElement.getSize(); + bufOffset += normalElement.getSize(); vertexFormat->setAttribute(gpu::Stream::TANGENT, tangentChannel, normalElement, bufOffset); - bufOffset = normalElement.getSize(); + bufOffset += normalElement.getSize(); if (!interleaveNormalsTangents) { bufOffset = 0; } @@ -895,7 +876,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { vertexBufferStream->addBuffer(attribBuffer, 0, vStride); } - // MEsh vertex format and vertex stream is ready + // Mesh vertex format and vertex stream is ready mesh->setVertexFormatAndStream(vertexFormat, vertexBufferStream); // Index and Part Buffers diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp index 6ce25bc56c..21553dc2c7 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp @@ -268,9 +268,18 @@ void GLBackend::updateInput() { auto offset = _input._bufferOffsets.data(); auto stride = _input._bufferStrides.data(); + // Profile the count of buffers to update and use it to short cut the for loop + int numInvalids = _input._invalidBuffers.count(); + _stats._ISNumInputBufferChanges += numInvalids; + PROFILE_COUNTER_IF_CHANGED(render_gpu, "numInputBuffersbound", int, numInvalids); + for (GLuint buffer = 0; buffer < _input._buffers.size(); buffer++, vbo++, offset++, stride++) { if (_input._invalidBuffers.test(buffer)) { glBindVertexBuffer(buffer, (*vbo), (*offset), (GLsizei)(*stride)); + numInvalids--; + if (numInvalids <= 0) { + break; + } } } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp index 9dcb08f0b7..30e393630a 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp @@ -78,7 +78,10 @@ void GL41Backend::updateInput() { const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); auto& inputChannels = _input._format->getChannels(); - _stats._ISNumInputBufferChanges++; + _stats._ISNumInputBufferChanges += _input._invalidBuffers.count(); + + // Profile the count of buffers to update + PROFILE_COUNTER_IF_CHANGED(render_gpu, "numInputBuffersbound", int, _input._invalidBuffers.count()); GLuint boundVBO = 0; for (auto& channelIt : inputChannels) { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp index c914b9f84d..de4989eb32 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp @@ -27,8 +27,6 @@ void GL45Backend::resetInputStage() { } void GL45Backend::updateInput() { - // PROFILE_RANGE(render_gpu, __FUNCTION__); - bool isStereoNow = isStereo(); // track stereo state change potentially happening wihtout changing the input format // this is a rare case requesting to invalid the format @@ -131,21 +129,26 @@ void GL45Backend::updateInput() { } if (_input._invalidBuffers.any()) { - // PROFILE_RANGE(render_gpu, "bindInputBuffers"); auto vbo = _input._bufferVBOs.data(); auto offset = _input._bufferOffsets.data(); auto stride = _input._bufferStrides.data(); - int numSet = 0; + // Profile the count of buffers to update and use it to short cut the for loop + int numInvalids = _input._invalidBuffers.count(); + _stats._ISNumInputBufferChanges += numInvalids; + PROFILE_COUNTER_IF_CHANGED(render_gpu, "numInputBuffersbound", int, numInvalids); + auto numBuffers = _input._buffers.size(); for (GLuint buffer = 0; buffer < numBuffers; buffer++, vbo++, offset++, stride++) { if (_input._invalidBuffers.test(buffer)) { glBindVertexBuffer(buffer, (*vbo), (*offset), (GLsizei)(*stride)); - numSet++; + numInvalids--; + if (numInvalids <= 0) { + break; + } } } - PROFILE_COUNTER_IF_CHANGED(render_gpu, "numVBSbound", int, numSet); _input._invalidBuffers.reset(); (void)CHECK_GL_ERROR(); From 77bb6fa4d54b0f7cccdcd6aa6b86d7fd58b963b4 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 5 Sep 2018 11:53:49 -0700 Subject: [PATCH 253/744] adding position to try again zone; updating whitespace --- interface/resources/serverless/redirect.json | 44 ++++++++++---------- libraries/networking/src/AddressManager.cpp | 4 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json index 48b1a7078f..64cb4d8a3f 100644 --- a/interface/resources/serverless/redirect.json +++ b/interface/resources/serverless/redirect.json @@ -9,39 +9,39 @@ "clientOnly": false, "collidesWith": "static,dynamic,kinematic,otherAvatar,", "collisionMask": 23, - "created": "2018-09-05T00:40:03Z", + "created": "2018-09-05T18:13:00Z", "dimensions": { - "blue": 0.8660923838615417, - "green": 1.921838402748108, - "red": 1.2744625806808472, - "x": 1.2744625806808472, - "y": 1.921838402748108, - "z": 0.8660923838615417 + "blue": 1.159199833869934, + "green": 2.8062009811401367, + "red": 1.6216505765914917, + "x": 1.6216505765914917, + "y": 2.8062009811401367, + "z": 1.159199833869934 }, - "id": "{781e26c7-ecfa-44b2-b7ef-4e3278787fbc}", - "lastEdited": 1536108183362142, - "lastEditedBy": "{4656d4a8-5e61-4230-ab34-2888d7945bd6}", + "id": "{d0ed60b8-9174-4c56-8e78-2c5399329ae0}", + "lastEdited": 1536171372916208, + "lastEditedBy": "{151cb20e-715a-4c80-aa0d-5b58b1c8a0c9}", "locked": true, "name": "Try Again Zone", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "position": { - "blue": 4.225754737854004, - "green": 1.7677892446517944, - "red": 2.778148889541626, - "x": 2.778148889541626, - "y": 1.7677892446517944, - "z": 4.225754737854004 + "blue":4.015342712402344, + "green":1.649999976158142, + "red":2.00921893119812, + "x":2.00921893119812, + "y":1.649999976158142, + "z":4.015342712402344 }, "queryAACube": { - "scale": 2.4632973670959473, - "x": 1.5465002059936523, - "y": 0.5361405611038208, - "z": 2.9941060543060303 + "scale": 3.4421300888061523, + "x": 1.6001315116882324, + "y": -0.07100248336791992, + "z": 0.14220571517944336 }, "rotation": { - "w": 0.9743700623512268, + "w": 0.9914448857307434, "x": 0, - "y": -0.22495104372501373, + "y": -0.13052619993686676, "z": 0 }, "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/zoneTryAgainEntityScript.js", diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 1b258e14b1..6ebd516063 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -259,8 +259,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_USER, lookupUrl.toString()); - // save the last visited domain URL. - _lastVisitedURL = lookupUrl; + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; // in case we're failing to connect to where we thought this user was // store their username as previous lookup so we can refresh their location via API From 133ac0662a57f8c35505fbd8327ba84b101aa705 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 10 Aug 2018 16:06:08 -0700 Subject: [PATCH 254/744] Move entity list qml to scripts dir --- scripts/system/libraries/EditEntityList.qml | 11 +++++++++++ scripts/system/libraries/entityList.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 scripts/system/libraries/EditEntityList.qml diff --git a/scripts/system/libraries/EditEntityList.qml b/scripts/system/libraries/EditEntityList.qml new file mode 100644 index 0000000000..d8099cb670 --- /dev/null +++ b/scripts/system/libraries/EditEntityList.qml @@ -0,0 +1,11 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtWebChannel 1.0 +import QtGraphicalEffects 1.0 +import "qrc:///qml/controls" as HifiControls + +HifiControls.WebView { + id: entityListToolWebView + url: Qt.resolvedUrl("../html/entityList.html") + enabled: true +} diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 678b2eeb0b..cd0c712148 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -20,7 +20,7 @@ EntityListTool = function(shouldUseEditTabletApp) { var ENTITY_LIST_WIDTH = 495; var MAX_DEFAULT_CREATE_TOOLS_HEIGHT = 778; var entityListWindow = new CreateWindow( - Script.resourcesPath() + "qml/hifi/tablet/EditEntityList.qml", + Script.resolvePath("EditEntityList.qml"), 'Entity List', 'com.highfidelity.create.entityListWindow', function () { From f6ac755bae0a6ce47b49d6445afb07fabefa40de Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 13 Aug 2018 08:47:37 -0700 Subject: [PATCH 255/744] Add profiling to edit.js fof the entity list --- scripts/system/html/js/entityList.js | 54 ++++++---- scripts/system/libraries/entityList.js | 130 ++++++++++++++----------- 2 files changed, 109 insertions(+), 75 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 5cd5f6d610..779100affb 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -23,6 +23,18 @@ const DELETE = 46; // Key code for the delete key. const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities. +var profileIndent = ''; +PROFILE = function(name, fn, args) { + EventBridge.emitWebEvent("PROFILE-Web " + profileIndent + "(" + name + ") Begin"); + var previousIndent = profileIndent; + profileIndent += ' '; + var before = Date.now(); + fn.apply(this, args); + var delta = Date.now() - before; + profileIndent = previousIndent; + EventBridge.emitWebEvent("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); +} + debugPrint = function (message) { console.log(message); }; @@ -363,27 +375,29 @@ function loaded() { refreshEntities(); } } else if (data.type === "update" && data.selectedIDs !== undefined) { - var newEntities = data.entities; - if (newEntities && newEntities.length == 0) { - elNoEntitiesMessage.style.display = "block"; - elFooter.firstChild.nodeValue = "0 entities found"; - } else if (newEntities) { - elNoEntitiesMessage.style.display = "none"; - for (var i = 0; i < newEntities.length; i++) { - var id = newEntities[i].id; - addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url, - newEntities[i].locked ? LOCKED_GLYPH : null, - newEntities[i].visible ? VISIBLE_GLYPH : null, - newEntities[i].verticesCount, newEntities[i].texturesCount, newEntities[i].texturesSize, - newEntities[i].hasTransparent ? TRANSPARENCY_GLYPH : null, - newEntities[i].isBaked ? BAKED_GLYPH : null, - newEntities[i].drawCalls, - newEntities[i].hasScript ? SCRIPT_GLYPH : null); + PROFILE("update", function() { + var newEntities = data.entities; + if (newEntities && newEntities.length == 0) { + elNoEntitiesMessage.style.display = "block"; + elFooter.firstChild.nodeValue = "0 entities found"; + } else if (newEntities) { + elNoEntitiesMessage.style.display = "none"; + for (var i = 0; i < newEntities.length; i++) { + var id = newEntities[i].id; + addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url, + newEntities[i].locked ? LOCKED_GLYPH : null, + newEntities[i].visible ? VISIBLE_GLYPH : null, + newEntities[i].verticesCount, newEntities[i].texturesCount, newEntities[i].texturesSize, + newEntities[i].hasTransparent ? TRANSPARENCY_GLYPH : null, + newEntities[i].isBaked ? BAKED_GLYPH : null, + newEntities[i].drawCalls, + newEntities[i].hasScript ? SCRIPT_GLYPH : null); + } + updateSelectedEntities(data.selectedIDs); + scheduleRefreshEntityList(); + resize(); } - updateSelectedEntities(data.selectedIDs); - scheduleRefreshEntityList(); - resize(); - } + }); } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { removeEntities(data.deletedIDs); updateSelectedEntities(data.selectedIDs); diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index cd0c712148..b52a31760f 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -11,6 +11,18 @@ /* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages, cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible */ +var profileIndent = ''; +PROFILE = function(name, fn, args) { + console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin"); + var previousIndent = profileIndent; + profileIndent += ' '; + var before = Date.now(); + fn.apply(this, args); + var delta = Date.now() - before; + profileIndent = previousIndent; + console.log("PROFILE-Script " + profileIndent + "(" + name + ") End " + delta + "ms"); +} + EntityListTool = function(shouldUseEditTabletApp) { var that = {}; @@ -66,11 +78,16 @@ EntityListTool = function(shouldUseEditTabletApp) { that.setVisible(false); function emitJSONScriptEvent(data) { - var dataString = JSON.stringify(data); - webView.emitScriptEvent(dataString); - if (entityListWindow.window) { - entityListWindow.window.emitScriptEvent(dataString); - } + var dataString; + PROFILE("Script-JSON.stringify", function() { + dataString = JSON.stringify(data); + }); + PROFILE("Script-emitScriptEvent", function() { + webView.emitScriptEvent(dataString); + if (entityListWindow.window) { + entityListWindow.window.emitScriptEvent(dataString); + } + }); } that.toggleVisible = function() { @@ -116,59 +133,61 @@ EntityListTool = function(shouldUseEditTabletApp) { } that.sendUpdate = function() { - var entities = []; + PROFILE('Script-sendUpdate', function() { + var entities = []; - var ids; - if (filterInView) { - ids = Entities.findEntitiesInFrustum(Camera.frustum); - } else { - ids = Entities.findEntities(MyAvatar.position, searchRadius); - } - - var cameraPosition = Camera.position; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - var properties = Entities.getEntityProperties(id); - - if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { - var url = ""; - if (properties.type === "Model") { - url = properties.modelURL; - } else if (properties.type === "Material") { - url = properties.materialURL; - } - entities.push({ - id: id, - name: properties.name, - type: properties.type, - url: url, - locked: properties.locked, - visible: properties.visible, - verticesCount: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.verticesCount) : ""), - texturesCount: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.texturesCount) : ""), - texturesSize: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.texturesSize) : ""), - hasTransparent: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.hasTransparent) : ""), - isBaked: properties.type === "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false, - drawCalls: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.drawCalls) : ""), - hasScript: properties.script !== "" - }); + var ids; + if (filterInView) { + ids = Entities.findEntitiesInFrustum(Camera.frustum); + } else { + ids = Entities.findEntities(MyAvatar.position, searchRadius); } - } - var selectedIDs = []; - for (var j = 0; j < selectionManager.selections.length; j++) { - selectedIDs.push(selectionManager.selections[j]); - } + var cameraPosition = Camera.position; + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + var properties = Entities.getEntityProperties(id); - emitJSONScriptEvent({ - type: "update", - entities: entities, - selectedIDs: selectedIDs, + if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { + var url = ""; + if (properties.type === "Model") { + url = properties.modelURL; + } else if (properties.type === "Material") { + url = properties.materialURL; + } + entities.push({ + id: id, + name: properties.name, + type: properties.type, + url: url, + locked: properties.locked, + visible: properties.visible, + verticesCount: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.verticesCount) : ""), + texturesCount: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.texturesCount) : ""), + texturesSize: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.texturesSize) : ""), + hasTransparent: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.hasTransparent) : ""), + isBaked: properties.type === "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false, + drawCalls: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.drawCalls) : ""), + hasScript: properties.script !== "" + }); + } + } + + var selectedIDs = []; + for (var j = 0; j < selectionManager.selections.length; j++) { + selectedIDs.push(selectionManager.selections[j]); + } + + emitJSONScriptEvent({ + type: "update", + entities: entities, + selectedIDs: selectedIDs, + }); }); }; @@ -186,7 +205,8 @@ EntityListTool = function(shouldUseEditTabletApp) { try { data = JSON.parse(data); } catch(e) { - print("entityList.js: Error parsing JSON: " + e.name + " data " + data); + console.log(data); + //print("entityList.js: Error parsing JSON: " + e.name + " data " + data); return; } From f2cc2f148597ecaff79ab5ab85c920fde9ad1a11 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 13 Aug 2018 13:21:00 -0700 Subject: [PATCH 256/744] Update entity list UI to be updated in a single batch --- scripts/system/html/entityList.html | 3 +- scripts/system/html/js/entityList.js | 127 ++++++++++++++++++--------- 2 files changed, 86 insertions(+), 44 deletions(-) diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 7906a3c97f..0802c23b71 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -10,8 +10,9 @@ - + + diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 779100affb..17a1b26bbf 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -23,16 +23,22 @@ const DELETE = 46; // Key code for the delete key. const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities. +log = function(msg) { + EventBridge.emitWebEvent(msg); +} + var profileIndent = ''; PROFILE = function(name, fn, args) { - EventBridge.emitWebEvent("PROFILE-Web " + profileIndent + "(" + name + ") Begin"); + log("PROFILE-Web " + profileIndent + "(" + name + ") Begin"); + console.log("PROFILE-Web " + profileIndent + "(" + name + ") Begin"); var previousIndent = profileIndent; profileIndent += ' '; var before = Date.now(); fn.apply(this, args); var delta = Date.now() - before; profileIndent = previousIndent; - EventBridge.emitWebEvent("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); + log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); + console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); } debugPrint = function (message) { @@ -168,44 +174,89 @@ function loaded() { return number ? number : ""; } - function addEntity(id, name, type, url, locked, visible, verticesCount, texturesCount, texturesSize, hasTransparent, - isBaked, drawCalls, hasScript) { + function getFilename(url) { + let urlParts = url.split('/'); + return urlParts[urlParts.length - 1]; + } - var urlParts = url.split('/'); - var filename = urlParts[urlParts.length - 1]; + //function addEntity( + //id, name, type, url, locked, visible, verticesCount, + //texturesCount, texturesSize, hasTransparent, + //isBaked, drawCalls, hasScript) { + function addEntities(entityData) { + const IMAGE_MODEL_NAME = 'default-image-model.fbx'; - var IMAGE_MODEL_NAME = 'default-image-model.fbx'; + let newEntities = entityData.filter(function(entity) { + if (entity.id in entities) { + var item = entities[entity.id].item; + item.values({ + name: entity.name, + url: getFilename(entity.url), + locked: entity.locked, + visible: entity.visible + }); + return false; + } + return true; + }); - if (filename === IMAGE_MODEL_NAME) { - type = "Image"; + if (newEntities.length === 0) { + return; } - if (entities[id] === undefined) { - entityList.add([{ - id: id, name: name, type: type, url: filename, locked: locked, visible: visible, - verticesCount: displayIfNonZero(verticesCount), texturesCount: displayIfNonZero(texturesCount), - texturesSize: decimalMegabytes(texturesSize), hasTransparent: hasTransparent, - isBaked: isBaked, drawCalls: displayIfNonZero(drawCalls), hasScript: hasScript - }], + newEntities = newEntities.map(function(entity) { + let type = entity.type; + let filename = getFilename(entity.url); + if (filename === IMAGE_MODEL_NAME) { + type = "Image"; + } + return { + id: entity.id, + name: entity.name, + type: type, + url: filename, + fullUrl: entity.url, + locked: entity.locked ? LOCKED_GLYPH : null, + visible: entity.visible ? VISIBLE_GLYPH : null, + verticesCount: displayIfNonZero(entity.verticesCount), + texturesCount: displayIfNonZero(entity.texturesCount), + texturesSize: decimalMegabytes(entity.texturesSize), + hasTransparent: entity.hasTransparent ? TRANSPARENCY_GLYPH : null, + isBaked: entity.isBaked ? BAKED_GLYPH : null, + drawCalls: displayIfNonZero(entity.drawCalls), + hasScript: entity.hasScript ? SCRIPT_GLYPH : null + } + }); + //newEntities = newEntities.splice(newEntities.length - 10); + console.log("Adding: " + newEntities.length); + + let size = 2000; + let sets = Math.ceil(newEntities.length / size); + for (let i = 0; i < sets; i++) { + + console.log(Date.now(), "Adding", i * size, (i + 1) * size); + entityList.add(newEntities.splice(i * size, (i + 1) * size), function (items) { - var currentElement = items[0].elm; - var id = items[0]._values.id; - entities[id] = { - id: id, - name: name, - el: currentElement, - item: items[0] - }; - currentElement.setAttribute('id', 'entity_' + id); - currentElement.setAttribute('title', url); - currentElement.dataset.entityId = id; - currentElement.onclick = onRowClicked; - currentElement.ondblclick = onRowDoubleClicked; + console.log(Date.now(), "added: " + items.length); + items.forEach(function(item) { + var currentElement = item.elm; + var values = item._values; + + entities[values.id] = { + id: values.id, + name: values.name, + el: currentElement, + item: item + }; + currentElement.setAttribute('id', 'entity_' + values.id); + currentElement.setAttribute('title', values.fullUrl); + currentElement.dataset.entityId = values.id; + currentElement.onclick = onRowClicked; + currentElement.ondblclick = onRowDoubleClicked; + }); }); - } else { - var item = entities[id].item; - item.values({ name: name, url: filename, locked: locked, visible: visible }); } + console.log(Date.now(), "DONE"); } function removeEntities(deletedIDs) { @@ -382,17 +433,7 @@ function loaded() { elFooter.firstChild.nodeValue = "0 entities found"; } else if (newEntities) { elNoEntitiesMessage.style.display = "none"; - for (var i = 0; i < newEntities.length; i++) { - var id = newEntities[i].id; - addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url, - newEntities[i].locked ? LOCKED_GLYPH : null, - newEntities[i].visible ? VISIBLE_GLYPH : null, - newEntities[i].verticesCount, newEntities[i].texturesCount, newEntities[i].texturesSize, - newEntities[i].hasTransparent ? TRANSPARENCY_GLYPH : null, - newEntities[i].isBaked ? BAKED_GLYPH : null, - newEntities[i].drawCalls, - newEntities[i].hasScript ? SCRIPT_GLYPH : null); - } + addEntities(newEntities); updateSelectedEntities(data.selectedIDs); scheduleRefreshEntityList(); resize(); From a9a5bf1e972cf654e34d4949348720ad9b9e28b8 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 20 Aug 2018 13:11:22 -0700 Subject: [PATCH 257/744] Replace entity list UI with custom, brute-force solution --- scripts/system/html/js/entityList.js | 243 ++++++++++++++----------- scripts/system/libraries/entityList.js | 4 + 2 files changed, 140 insertions(+), 107 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 17a1b26bbf..c30aac2a7f 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -6,12 +6,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -var entities = {}; -var selectedEntities = []; -var currentSortColumn = 'type'; -var currentSortOrder = 'des'; -var entityList = null; -var refreshEntityListTimer = null; +const ASCENDING_SORT = 1; +const DESCENDING_SORT = -1; const ASCENDING_STRING = '▴'; const DESCENDING_STRING = '▾'; const LOCKED_GLYPH = ""; @@ -23,6 +19,30 @@ const DELETE = 46; // Key code for the delete key. const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities. +const COMPARE_ASCENDING = function(a, b) { + let va = a[currentSortColumn]; + let vb = b[currentSortColumn]; + + if (va < vb) { + return -1; + } else if (va > vb) { + return 1; + } + return 0; +} +const COMPARE_DESCENDING = function(a, b) { + return COMPARE_ASCENDING(b, a); +} + +var entities = {}; +var entityCount = 0; +// Raw entity data sent from script +let entityData = [] +var selectedEntities = []; +var currentSortColumn = 'type'; +var currentSortOrder = ASCENDING_SORT; +var refreshEntityListTimer = null; + log = function(msg) { EventBridge.emitWebEvent(msg); } @@ -47,8 +67,6 @@ debugPrint = function (message) { function loaded() { openEventBridge(function() { - entityList = new List('entity-list', { valueNames: ['name', 'type', 'url', 'locked', 'visible'], page: MAX_ITEMS}); - entityList.clear(); elEntityTable = document.getElementById("entity-table"); elEntityTableBody = document.getElementById("entity-table-body"); elRefresh = document.getElementById("refresh"); @@ -107,10 +125,12 @@ function loaded() { }; function onRowClicked(clickEvent) { - var id = this.dataset.entityId; - var selection = [this.dataset.entityId]; + let entityID = this.dataset.entityID; + console.log("CLICKED", entityID, this); + //return; + var selection = [entityID]; if (clickEvent.ctrlKey) { - var selectedIndex = selectedEntities.indexOf(id); + var selectedIndex = selectedEntities.indexOf(entityID); if (selectedIndex >= 0) { selection = selectedEntities; selection.splice(selectedIndex, 1) @@ -121,7 +141,7 @@ function loaded() { var previousItemFound = -1; var clickedItemFound = -1; for (var entity in entityList.visibleItems) { - if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) { + if (clickedItemFound === -1 && entityID == entityList.visibleItems[entity].values().id) { clickedItemFound = entity; } else if(previousItemFound === -1 && selectedEntities[0] == entityList.visibleItems[entity].values().id) { previousItemFound = entity; @@ -143,6 +163,12 @@ function loaded() { } } + selectedEntities.forEach(function(entityID) { + if (selection.indexOf(entityID) === -1) { + entities[entityID].el.className = ''; + } + }); + selectedEntities = selection; this.className = 'selected'; @@ -160,7 +186,7 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", focus: true, - entityIds: [this.dataset.entityId], + entityIds: [this.dataset.entityID], })); } @@ -179,90 +205,88 @@ function loaded() { return urlParts[urlParts.length - 1]; } - //function addEntity( - //id, name, type, url, locked, visible, verticesCount, - //texturesCount, texturesSize, hasTransparent, - //isBaked, drawCalls, hasScript) { - function addEntities(entityData) { + function refreshEntityList() { const IMAGE_MODEL_NAME = 'default-image-model.fbx'; - let newEntities = entityData.filter(function(entity) { - if (entity.id in entities) { - var item = entities[entity.id].item; - item.values({ + PROFILE("sort", function() { + let cmp = currentSortOrder === ASCENDING_SORT ? COMPARE_ASCENDING : COMPARE_DESCENDING; + console.log("Doing sort", currentSortColumn, currentSortOrder); + entityData.sort(cmp); + }); + + entities = {}; + + let newEntities; + PROFILE("map-data", function() { + newEntities = entityData.map(function(entity) { + let type = entity.type; + let filename = getFilename(entity.url); + if (filename === IMAGE_MODEL_NAME) { + type = "Image"; + } + return { + id: entity.id, name: entity.name, - url: getFilename(entity.url), - locked: entity.locked, - visible: entity.visible - }); - return false; - } - return true; - }); - - if (newEntities.length === 0) { - return; - } - - newEntities = newEntities.map(function(entity) { - let type = entity.type; - let filename = getFilename(entity.url); - if (filename === IMAGE_MODEL_NAME) { - type = "Image"; - } - return { - id: entity.id, - name: entity.name, - type: type, - url: filename, - fullUrl: entity.url, - locked: entity.locked ? LOCKED_GLYPH : null, - visible: entity.visible ? VISIBLE_GLYPH : null, - verticesCount: displayIfNonZero(entity.verticesCount), - texturesCount: displayIfNonZero(entity.texturesCount), - texturesSize: decimalMegabytes(entity.texturesSize), - hasTransparent: entity.hasTransparent ? TRANSPARENCY_GLYPH : null, - isBaked: entity.isBaked ? BAKED_GLYPH : null, - drawCalls: displayIfNonZero(entity.drawCalls), - hasScript: entity.hasScript ? SCRIPT_GLYPH : null - } - }); - //newEntities = newEntities.splice(newEntities.length - 10); - console.log("Adding: " + newEntities.length); - - let size = 2000; - let sets = Math.ceil(newEntities.length / size); - for (let i = 0; i < sets; i++) { - - console.log(Date.now(), "Adding", i * size, (i + 1) * size); - entityList.add(newEntities.splice(i * size, (i + 1) * size), - function (items) { - console.log(Date.now(), "added: " + items.length); - items.forEach(function(item) { - var currentElement = item.elm; - var values = item._values; - - entities[values.id] = { - id: values.id, - name: values.name, - el: currentElement, - item: item - }; - currentElement.setAttribute('id', 'entity_' + values.id); - currentElement.setAttribute('title', values.fullUrl); - currentElement.dataset.entityId = values.id; - currentElement.onclick = onRowClicked; - currentElement.ondblclick = onRowDoubleClicked; - }); + type: type, + url: filename, + fullUrl: entity.url, + locked: entity.locked ? LOCKED_GLYPH : null, + visible: entity.visible ? VISIBLE_GLYPH : null, + verticesCount: displayIfNonZero(entity.verticesCount), + texturesCount: displayIfNonZero(entity.texturesCount), + texturesSize: decimalMegabytes(entity.texturesSize), + hasTransparent: entity.hasTransparent ? TRANSPARENCY_GLYPH : null, + isBaked: entity.isBaked ? BAKED_GLYPH : null, + drawCalls: displayIfNonZero(entity.drawCalls), + hasScript: entity.hasScript ? SCRIPT_GLYPH : null + } }); - } - console.log(Date.now(), "DONE"); + }); + + console.log("Adding: " + newEntities.length); + + elEntityTableBody.innerHTML = ''; + + entities = {}; + + PROFILE("update-dom", function() { + newEntities.forEach(function(entity) { + let row = document.createElement('tr'); + row.dataset.entityID = entity.id; + row.attributes.title = entity.fullUrl; + function addColumn(cls, text) { + let col = document.createElement('td'); + col.className = cls; + col.innerText = text; + row.append(col); + } + addColumn('type', entity.type); + addColumn('name', entity.name); + addColumn('url', entity.url); + addColumn('locked glyph', entity.locked); + addColumn('visible glyph', entity.visible); + addColumn('verticesCount', entity.verticesCount); + addColumn('texturesCount', entity.texturesCount); + addColumn('texturesSize', entity.texturesSize); + addColumn('hasTransparent glyph', entity.hasTransparent); + addColumn('isBaked glyph', entity.isBaked); + addColumn('drawCalls', entity.drawCalls); + addColumn('hasScript glyph', entity.hasScript); + elEntityTableBody.append(row); + row.addEventListener('click', onRowClicked); + row.addEventListener('dblclick', onRowDoubleClicked); + entities[entity.id] = { el: row }; + }); + + }); } function removeEntities(deletedIDs) { + return; for (i = 0, length = deletedIDs.length; i < length; i++) { - delete entities[deletedIDs[i]]; - entityList.remove("id", deletedIDs[i]); + let id = deletedIDs[i]; + entities[id].el.remove(); + delete entities[id]; } } @@ -276,7 +300,6 @@ function loaded() { function clearEntities() { entities = {}; - entityList.clear(); refreshFooter(); } @@ -295,15 +318,19 @@ function loaded() { hasScript: document.querySelector('#entity-hasScript .sort-order'), } function setSortColumn(column) { - if (currentSortColumn == column) { - currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc"; - } else { - elSortOrder[currentSortColumn].innerHTML = ""; - currentSortColumn = column; - currentSortOrder = "asc"; - } - elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASCENDING_STRING : DESCENDING_STRING; - entityList.sort(currentSortColumn, { order: currentSortOrder }); + PROFILE("set-sort-column", function() { + if (currentSortColumn == column) { + currentSortOrder *= -1; + } else { + elSortOrder[currentSortColumn].innerHTML = ""; + currentSortColumn = column; + currentSortOrder = ASCENDING_SORT; + } + elSortOrder[column].innerHTML = currentSortOrder == ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING; + + //entityList.sort(currentSortColumn, { order: currentSortOrder }); + refreshEntityList(); + }); } setSortColumn('type'); @@ -313,21 +340,22 @@ function loaded() { } function refreshFooter() { + return; if (selectedEntities.length > 1) { elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected"; } else if (selectedEntities.length === 1) { elFooter.firstChild.nodeValue = "1 entity selected"; - } else if (entityList.visibleItems.length === 1) { + } else if (entityCount === 1) { elFooter.firstChild.nodeValue = "1 entity found"; } else { - elFooter.firstChild.nodeValue = entityList.visibleItems.length + " entities found"; + elFooter.firstChild.nodeValue = entityCount + " entities found"; } } function refreshEntityListObject() { refreshEntityListTimer = null; - entityList.sort(currentSortColumn, { order: currentSortOrder }); - entityList.search(elFilter.value); + //entityList.sort(currentSortColumn, { order: currentSortOrder }); + //entityList.search(elFilter.value); refreshFooter(); } @@ -433,10 +461,11 @@ function loaded() { elFooter.firstChild.nodeValue = "0 entities found"; } else if (newEntities) { elNoEntitiesMessage.style.display = "none"; - addEntities(newEntities); - updateSelectedEntities(data.selectedIDs); - scheduleRefreshEntityList(); - resize(); + entityData = newEntities; + refreshEntityList(); + //updateSelectedEntities(data.selectedIDs); + //scheduleRefreshEntityList(); + //resize(); } }); } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index b52a31760f..1651eaedcc 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -137,13 +137,16 @@ EntityListTool = function(shouldUseEditTabletApp) { var entities = []; var ids; + PROFILE("findEntities", function() { if (filterInView) { ids = Entities.findEntitiesInFrustum(Camera.frustum); } else { ids = Entities.findEntities(MyAvatar.position, searchRadius); } + }); var cameraPosition = Camera.position; + PROFILE("getProperties", function() { for (var i = 0; i < ids.length; i++) { var id = ids[i]; var properties = Entities.getEntityProperties(id); @@ -177,6 +180,7 @@ EntityListTool = function(shouldUseEditTabletApp) { }); } } + }); var selectedIDs = []; for (var j = 0; j < selectionManager.selections.length; j++) { From 739530cb85c75344e18732cd6a057cfe6b40191f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 21 Aug 2018 09:30:19 -0700 Subject: [PATCH 258/744] Limit Entity List to only getting the properties it needs --- scripts/system/libraries/entityList.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 1651eaedcc..3900260d0c 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -82,6 +82,7 @@ EntityListTool = function(shouldUseEditTabletApp) { PROFILE("Script-JSON.stringify", function() { dataString = JSON.stringify(data); }); + console.log("Length: ", dataString.length, data.type); PROFILE("Script-emitScriptEvent", function() { webView.emitScriptEvent(dataString); if (entityListWindow.window) { @@ -149,7 +150,9 @@ EntityListTool = function(shouldUseEditTabletApp) { PROFILE("getProperties", function() { for (var i = 0; i < ids.length; i++) { var id = ids[i]; - var properties = Entities.getEntityProperties(id); + //var properties = Entities.getEntityProperties(id); + var properties = Entities.getEntityProperties(id, ['name', 'type', 'locked', + 'visible', 'renderInfo', 'type', 'modelURL', 'materialURL', 'script']); if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { var url = ""; From 335f66d165f2c533def5f10e17ca17f3395117a0 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 5 Sep 2018 14:28:57 -0700 Subject: [PATCH 259/744] Add filtering support to entity list --- scripts/system/html/entityList.html | 1 + scripts/system/html/js/entityList.js | 237 +++++++++++++++------------ 2 files changed, 131 insertions(+), 107 deletions(-) diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 0802c23b71..cb95dffc5f 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -9,6 +9,7 @@ --> + diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index c30aac2a7f..ebf31d5d36 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -34,22 +34,24 @@ const COMPARE_DESCENDING = function(a, b) { return COMPARE_ASCENDING(b, a); } -var entities = {}; -var entityCount = 0; -// Raw entity data sent from script -let entityData = [] +//console.log = function() { }; + + + +// List of all entities +let entities = [] +// List of all entities, indexed by Entity ID +var entitiesByID = {}; +// The filtered and sorted list of entities +var visibleEntities = []; + var selectedEntities = []; var currentSortColumn = 'type'; var currentSortOrder = ASCENDING_SORT; -var refreshEntityListTimer = null; - -log = function(msg) { - EventBridge.emitWebEvent(msg); -} +const ENABLE_PROFILING = true; var profileIndent = ''; -PROFILE = function(name, fn, args) { - log("PROFILE-Web " + profileIndent + "(" + name + ") Begin"); +const PROFILE = !ENABLE_PROFILING ? function() { } : function(name, fn, args) { console.log("PROFILE-Web " + profileIndent + "(" + name + ") Begin"); var previousIndent = profileIndent; profileIndent += ' '; @@ -57,9 +59,8 @@ PROFILE = function(name, fn, args) { fn.apply(this, args); var delta = Date.now() - before; profileIndent = previousIndent; - log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); -} +}; debugPrint = function (message) { console.log(message); @@ -127,10 +128,9 @@ function loaded() { function onRowClicked(clickEvent) { let entityID = this.dataset.entityID; console.log("CLICKED", entityID, this); - //return; - var selection = [entityID]; + let selection = [entityID]; if (clickEvent.ctrlKey) { - var selectedIndex = selectedEntities.indexOf(entityID); + let selectedIndex = selectedEntities.indexOf(entityID); if (selectedIndex >= 0) { selection = selectedEntities; selection.splice(selectedIndex, 1) @@ -138,22 +138,23 @@ function loaded() { selection = selection.concat(selectedEntities); } } else if (clickEvent.shiftKey && selectedEntities.length > 0) { - var previousItemFound = -1; - var clickedItemFound = -1; - for (var entity in entityList.visibleItems) { - if (clickedItemFound === -1 && entityID == entityList.visibleItems[entity].values().id) { - clickedItemFound = entity; - } else if(previousItemFound === -1 && selectedEntities[0] == entityList.visibleItems[entity].values().id) { - previousItemFound = entity; + let previousItemFound = -1; + let clickedItemFound = -1; + for (let i = 0, len = visibleEntities.length; i < len; ++i) { + let entity = visibleEntities[i]; + if (clickedItemFound === -1 && entityID == entity.id) { + clickedItemFound = i; + } else if (previousItemFound === -1 && selectedEntities[0] === entity.id) { + previousItemFound = i; } - } + }; if (previousItemFound !== -1 && clickedItemFound !== -1) { - var betweenItems = []; - var toItem = Math.max(previousItemFound, clickedItemFound); + let betweenItems = []; + let toItem = Math.max(previousItemFound, clickedItemFound); // skip first and last item in this loop, we add them to selection after the loop - for (var i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) { - entityList.visibleItems[i].elm.className = 'selected'; - betweenItems.push(entityList.visibleItems[i].values().id); + for (let i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) { + visibleEntities[i].el.className = 'selected'; + betweenItems.push(visibleEntities[i].id); } if (previousItemFound > clickedItemFound) { // always make sure that we add the items in the right order @@ -165,7 +166,7 @@ function loaded() { selectedEntities.forEach(function(entityID) { if (selection.indexOf(entityID) === -1) { - entities[entityID].el.className = ''; + entitiesByID[entityID].el.className = ''; } }); @@ -205,26 +206,28 @@ function loaded() { return urlParts[urlParts.length - 1]; } - function refreshEntityList() { + elFilter.onkeyup = refreshEntityList; + elFilter.onpaste = refreshEntityList; + elFilter.onchange = refreshEntityList; + + // Update the entity list with the new set of data sent from edit.js + function updateEntityList(entityData) { + console.warn("updating entity list"); const IMAGE_MODEL_NAME = 'default-image-model.fbx'; - PROFILE("sort", function() { - let cmp = currentSortOrder === ASCENDING_SORT ? COMPARE_ASCENDING : COMPARE_DESCENDING; - console.log("Doing sort", currentSortColumn, currentSortOrder); - entityData.sort(cmp); - }); + entities = [] + entitiesByID = {}; + visibleEntities = []; - entities = {}; - - let newEntities; PROFILE("map-data", function() { - newEntities = entityData.map(function(entity) { + entityData.forEach(function(entity) { let type = entity.type; let filename = getFilename(entity.url); if (filename === IMAGE_MODEL_NAME) { type = "Image"; } - return { + + let entityData = { id: entity.id, name: entity.name, type: type, @@ -238,46 +241,83 @@ function loaded() { hasTransparent: entity.hasTransparent ? TRANSPARENCY_GLYPH : null, isBaked: entity.isBaked ? BAKED_GLYPH : null, drawCalls: displayIfNonZero(entity.drawCalls), - hasScript: entity.hasScript ? SCRIPT_GLYPH : null + hasScript: entity.hasScript ? SCRIPT_GLYPH : null, } + + entities.push(entityData); + entitiesByID[entityData.id] = entityData; }); }); - console.log("Adding: " + newEntities.length); + PROFILE("create-rows", function() { + entities.forEach(function(entity) { + let row = document.createElement('tr'); + row.dataset.entityID = entity.id; + row.attributes.title = entity.fullUrl; + function addColumn(cls, text) { + let col = document.createElement('td'); + col.className = cls; + col.innerText = text; + row.append(col); + } + function addColumnHTML(cls, text) { + let col = document.createElement('td'); + col.className = cls; + col.innerHTML = text; + row.append(col); + } + addColumn('type', entity.type); + addColumn('name', entity.name); + addColumn('url', entity.url); + addColumnHTML('locked glyph', entity.locked); + addColumnHTML('visible glyph', entity.visible); + addColumn('verticesCount', entity.verticesCount); + addColumn('texturesCount', entity.texturesCount); + addColumn('texturesSize', entity.texturesSize); + addColumnHTML('hasTransparent glyph', entity.hasTransparent); + addColumnHTML('isBaked glyph', entity.isBaked); + addColumn('drawCalls', entity.drawCalls); + addColumn('hasScript glyph', entity.hasScript); + row.addEventListener('click', onRowClicked); + row.addEventListener('dblclick', onRowDoubleClicked); - elEntityTableBody.innerHTML = ''; - - entities = {}; - - PROFILE("update-dom", function() { - newEntities.forEach(function(entity) { - let row = document.createElement('tr'); - row.dataset.entityID = entity.id; - row.attributes.title = entity.fullUrl; - function addColumn(cls, text) { - let col = document.createElement('td'); - col.className = cls; - col.innerText = text; - row.append(col); - } - addColumn('type', entity.type); - addColumn('name', entity.name); - addColumn('url', entity.url); - addColumn('locked glyph', entity.locked); - addColumn('visible glyph', entity.visible); - addColumn('verticesCount', entity.verticesCount); - addColumn('texturesCount', entity.texturesCount); - addColumn('texturesSize', entity.texturesSize); - addColumn('hasTransparent glyph', entity.hasTransparent); - addColumn('isBaked glyph', entity.isBaked); - addColumn('drawCalls', entity.drawCalls); - addColumn('hasScript glyph', entity.hasScript); - elEntityTableBody.append(row); - row.addEventListener('click', onRowClicked); - row.addEventListener('dblclick', onRowDoubleClicked); - entities[entity.id] = { el: row }; + entity.el = row; + }); }); - + + refreshEntityList(); + updateSelectedEntities(selectedEntities); + } + + function refreshEntityList() { + PROFILE("refresh-entity-list", function() { + console.warn("refreshing entity list"); + PROFILE("filter", function() { + let searchTerm = elFilter.value; + if (searchTerm === '') { + visibleEntities = entities.slice(0); + } else { + console.log("Filtering on: ", searchTerm); + visibleEntities = entities.filter(function(e) { + return e.name.indexOf(searchTerm) > -1 + || e.type.indexOf(searchTerm) > -1 + || e.fullUrl.indexOf(searchTerm) > -1; + }); + } + }); + + PROFILE("sort", function() { + let cmp = currentSortOrder === ASCENDING_SORT ? COMPARE_ASCENDING : COMPARE_DESCENDING; + console.log("Doing sort", currentSortColumn, currentSortOrder); + visibleEntities.sort(cmp); + }); + + PROFILE("update-dom", function() { + elEntityTableBody.innerHTML = ''; + for (let i = 0, len = visibleEntities.length; i < len; ++i) { + elEntityTableBody.append(visibleEntities[i].el); + } + }); }); } @@ -290,14 +330,6 @@ function loaded() { } } - function scheduleRefreshEntityList() { - var REFRESH_DELAY = 50; - if (refreshEntityListTimer) { - clearTimeout(refreshEntityListTimer); - } - refreshEntityListTimer = setTimeout(refreshEntityListObject, REFRESH_DELAY); - } - function clearEntities() { entities = {}; refreshFooter(); @@ -327,8 +359,6 @@ function loaded() { currentSortOrder = ASCENDING_SORT; } elSortOrder[column].innerHTML = currentSortOrder == ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING; - - //entityList.sort(currentSortColumn, { order: currentSortOrder }); refreshEntityList(); }); } @@ -340,37 +370,31 @@ function loaded() { } function refreshFooter() { - return; if (selectedEntities.length > 1) { elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected"; } else if (selectedEntities.length === 1) { elFooter.firstChild.nodeValue = "1 entity selected"; - } else if (entityCount === 1) { + } else if (visibleEntities.length === 1) { elFooter.firstChild.nodeValue = "1 entity found"; } else { - elFooter.firstChild.nodeValue = entityCount + " entities found"; + elFooter.firstChild.nodeValue = visibleEntities.length + " entities found"; } } - function refreshEntityListObject() { - refreshEntityListTimer = null; - //entityList.sort(currentSortColumn, { order: currentSortOrder }); - //entityList.search(elFilter.value); - refreshFooter(); - } function updateSelectedEntities(selectedIDs) { - var notFound = false; - for (var id in entities) { - entities[id].el.className = ''; - } + let notFound = false; + + selectedEntities.forEach(function(id) { + entitiesByID[id].el.className = ''; + }); selectedEntities = []; - for (var i = 0; i < selectedIDs.length; i++) { - var id = selectedIDs[i]; + for (let i = 0; i < selectedIDs.length; i++) { + let id = selectedIDs[i]; selectedEntities.push(id); - if (id in entities) { - var entity = entities[id]; + if (id in entitiesByID) { + let entity = entitiesByID[id]; entity.el.className = 'selected'; } else { notFound = true; @@ -461,17 +485,16 @@ function loaded() { elFooter.firstChild.nodeValue = "0 entities found"; } else if (newEntities) { elNoEntitiesMessage.style.display = "none"; - entityData = newEntities; - refreshEntityList(); - //updateSelectedEntities(data.selectedIDs); - //scheduleRefreshEntityList(); + //entityData = newEntities; + updateEntityList(newEntities); + //refreshEntityList(); + updateSelectedEntities(data.selectedIDs); //resize(); } }); } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { removeEntities(data.deletedIDs); updateSelectedEntities(data.selectedIDs); - scheduleRefreshEntityList(); } else if (data.type === "deleted" && data.ids) { removeEntities(data.ids); refreshFooter(); From 1feaf286b43e27f5d077946232a449fa56c96f37 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Wed, 5 Sep 2018 14:35:50 -0700 Subject: [PATCH 260/744] keeping position separated from the other attributes --- libraries/fbx/src/FBXReader_Mesh.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index ef2fcc23b4..bb95fb42f7 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -763,8 +763,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { bool interleaveNormalsTangents = true; // If has blend shapes allocate and assign buffers for pos and tangents now + hasBlendShapes = true; if (hasBlendShapes) { - auto posBuffer = std::make_shared(); posBuffer->setData(positionsSize, (const gpu::Byte*) vertBuffer->getData() + positionsOffset); vertexBufferStream->addBuffer(posBuffer, 0, positionElement.getSize()); @@ -781,6 +781,19 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { attribChannel = 2; totalAttribBufferSize = totalVertsSize - positionsSize - normalsAndTangentsSize; + } else { + auto posBuffer = std::make_shared(); + posBuffer->setData(positionsSize, (const gpu::Byte*) vertBuffer->getData() + positionsOffset); + vertexBufferStream->addBuffer(posBuffer, 0, positionElement.getSize()); + + // update channels and attribBuffer size accordingly + interleavePositions = false; + interleaveNormalsTangents = true; + + tangentChannel = 1; + attribChannel = 1; + + totalAttribBufferSize = totalVertsSize - positionsSize; } // Define the vertex format, compute the offset for each attributes as we append them to the vertex format From dcf2e00bd7b38482fde9d69bf41d42ccfc72fd1f Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Wed, 5 Sep 2018 14:55:12 -0700 Subject: [PATCH 261/744] Adding placeholder text --- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 4c6e5f6fce..55d157fdd8 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -124,6 +124,7 @@ Item { width: parent.width focus: true label: "Username or Email" + placeholderText: "Username or Email" activeFocusOnPress: true ShortcutText { @@ -151,8 +152,8 @@ Item { TextField { id: passwordField width: parent.width - label: "Password" + placeholderText: "Password" echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password activeFocusOnPress: true From 398724de73698da6dc8c7cbea0acae317f60a625 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Sep 2018 10:23:45 +1200 Subject: [PATCH 262/744] Make Save in HMD settings dialogs return to previous dialog Same as Cancel key does. --- .../hifi/tablet/tabletWindows/TabletPreferencesDialog.qml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml index 31d246be2d..5a0dc24dbc 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -41,7 +41,11 @@ Item { section.saveAll(); } - closeDialog(); + if (HMD.active) { + tablet.popFromStack(); + } else { + closeDialog(); + } } function restoreAll() { From b022697dfa245bcaadaf31c169b4d071d1e5fd17 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 5 Sep 2018 15:36:07 -0700 Subject: [PATCH 263/744] volume slider on users fixed --- interface/resources/qml/hifi/NameCard.qml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index abcceae295..dfa6555150 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -530,9 +530,7 @@ Item { maximumValue: 20.0 stepSize: 5 updateValueWhileDragging: true - Component.onCompleted: { - value = Users.getAvatarGain(uuid); - } + value: Users.getAvatarGain(uuid) onValueChanged: { updateGainFromQML(uuid, value, false); } From 960bf00e1200492920bee9336749514c72339750 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Sep 2018 11:02:24 +1200 Subject: [PATCH 264/744] Modify look of HMD mode Settings dialogs' titles --- .../resources/qml/hifi/tablet/TabletMenu.qml | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index bfbd2d8813..6540d53fca 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -49,11 +49,11 @@ FocusScope { HiFiGlyphs { id: menuRootIcon - text: hifi.glyphs.backward + text: breadcrumbText.text !== "Menu" ? hifi.glyphs.backward : "" size: 72 anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - width: breadcrumbText.text === "Menu" ? 15 : 50 + width: breadcrumbText.text === "Menu" ? 32 : 50 visible: breadcrumbText.text !== "Menu" MouseArea { @@ -79,23 +79,10 @@ FocusScope { id: breadcrumbText text: "Menu" size: 26 - color: "#34a2c7" + color: "#e3e3e3" anchors.verticalCenter: parent.verticalCenter anchors.left: menuRootIcon.right anchors.leftMargin: 15 - MouseArea { - anchors.fill: parent - hoverEnabled: true - onEntered: breadcrumbText.color = "#1fc6a6"; - onExited: breadcrumbText.color = "#34a2c7"; - // navigate back to parent level menu if there is one - onClicked: { - if (breadcrumbText.text !== "Menu") { - menuPopperUpper.closeLastMenu(); - } - tabletRoot.playButtonClickSound(); - } - } } } From a7099025dcfee39fd8ccd4603e93390dc9f074d8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Sep 2018 11:15:14 +1200 Subject: [PATCH 265/744] Fix up some Settings dialogs' titles --- interface/resources/qml/hifi/audio/Audio.qml | 2 +- interface/resources/qml/hifi/tablet/ControllerSettings.qml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index cc1ba49984..f4a708567a 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -26,7 +26,7 @@ Rectangle { HifiConstants { id: hifi; } property var eventBridge; - property string title: "Audio Settings - " + AudioScriptingInterface.context; + property string title: "Audio Settings" signal sendToScript(var message); color: hifi.colors.baseGray; diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index 135c1379e2..6706830537 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -24,6 +24,8 @@ Item { height: parent.height width: parent.width + property string title: "Controls" + HifiConstants { id: hifi } TabBar { From 1e697ad45f139d281012d87d4099d90b80f32625 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Wed, 5 Sep 2018 16:25:18 -0700 Subject: [PATCH 266/744] Removing labels, adding flavor text, moving links --- .../qml/LoginDialog/LinkAccountBody.qml | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 55d157fdd8..f6ad5385c0 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -87,6 +87,21 @@ Item { height: 48 } + FlavorText { + id: flavorTextContainer + anchors { + top: parent.top + margins: 0 + topMargin: hifi.dimensions.contentSpacing.y + } + + text: qsTr("Sign in to High Fidelity to make friends, get HFC, and buy interesting things on the Marketplace!") + wrapMode: Text.WordWrap + lineHeight: 2 + lineHeightMode: Text.ProportionalHeight + horizontalAlignment: Text.AlignHCenter + } + ShortcutText { id: mainTextContainer anchors { @@ -97,7 +112,6 @@ Item { } visible: false - text: qsTr("Username or password incorrect.") wrapMode: Text.WordWrap color: hifi.colors.redAccent @@ -123,15 +137,14 @@ Item { text: Settings.getValue("wallet/savedUsername", ""); width: parent.width focus: true - label: "Username or Email" placeholderText: "Username or Email" activeFocusOnPress: true ShortcutText { z: 10 anchors { - left: usernameField.left - top: usernameField.top + left: usernameField.right + top: usernameField.bottom leftMargin: usernameField.textFieldLabel.contentWidth + 10 topMargin: -19 } @@ -152,7 +165,6 @@ Item { TextField { id: passwordField width: parent.width - label: "Password" placeholderText: "Password" echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password activeFocusOnPress: true @@ -160,8 +172,8 @@ Item { ShortcutText { z: 10 anchors { - left: passwordField.left - top: passwordField.top + left: passwordField.right + top: passwordField.bottom leftMargin: passwordField.textFieldLabel.contentWidth + 10 topMargin: -19 } From bb8d9c5cf0a18845b4ffcdfe3d03eb30382327ae Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Wed, 5 Sep 2018 16:50:08 -0700 Subject: [PATCH 267/744] For stories, base the notification popups off of the action_string instead of a hard-coded value. --- scripts/system/tablet-goto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 46ddeb2bab..80b4e67c43 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -127,7 +127,7 @@ return; } stories[story.id] = story; - var message = story.username + " says something is happening in " + story.place_name + ". Open GOTO to join them."; + var message = story.username + " " + story.action_string + " " + story.place_name + ". Open GOTO to join them."; Window.displayAnnouncement(message); didNotify = true; }); From e8ce1526876cfbba3c883891e9aa1165ea24adb6 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 5 Sep 2018 14:46:07 -0700 Subject: [PATCH 268/744] Scale collision picks relative to parent by adding and using more specialized transform nodes. --- .../src/raypick/PickScriptingInterface.cpp | 16 ++++++++-- .../src/ui/overlays/OverlayTransformNode.cpp | 30 +++++++++++++++++++ .../src/ui/overlays/OverlayTransformNode.h | 26 ++++++++++++++++ .../avatars-renderer/AvatarTransformNode.cpp | 30 +++++++++++++++++++ .../avatars-renderer/AvatarTransformNode.h | 25 ++++++++++++++++ .../entities/src/EntityTransformNode.cpp | 30 +++++++++++++++++++ libraries/entities/src/EntityTransformNode.h | 25 ++++++++++++++++ 7 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 interface/src/ui/overlays/OverlayTransformNode.cpp create mode 100644 interface/src/ui/overlays/OverlayTransformNode.h create mode 100644 libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp create mode 100644 libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.h create mode 100644 libraries/entities/src/EntityTransformNode.cpp create mode 100644 libraries/entities/src/EntityTransformNode.h diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 6dedf3fca1..c3a783a72b 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -24,11 +24,13 @@ #include "CollisionPick.h" #include "SpatialParentFinder.h" -#include "NestableTransformNode.h" #include "PickTransformNode.h" #include "MouseTransformNode.h" #include "avatar/MyAvatarHeadTransformNode.h" #include "avatar/AvatarManager.h" +#include "avatars-renderer/AvatarTransformNode.h" +#include "ui/overlays/OverlayTransformNode.h" +#include "EntityTransformNode.h" #include @@ -375,6 +377,16 @@ std::shared_ptr PickScriptingInterface::createTransformNode(const } auto sharedNestablePointer = nestablePointer.lock(); if (success && sharedNestablePointer) { + NestableType nestableType = sharedNestablePointer->getNestableType(); + if (nestableType == NestableType::Avatar) { + return std::make_shared(std::static_pointer_cast(sharedNestablePointer), parentJointIndex); + } + if (nestableType == NestableType::Overlay) { + return std::make_shared(std::static_pointer_cast(sharedNestablePointer), parentJointIndex); + } + if (nestableType == NestableType::Entity) { + return std::make_shared(std::static_pointer_cast(sharedNestablePointer), parentJointIndex); + } return std::make_shared(nestablePointer, parentJointIndex); } } @@ -394,7 +406,7 @@ std::shared_ptr PickScriptingInterface::createTransformNode(const } else if (!joint.isNull()) { auto myAvatar = DependencyManager::get()->getMyAvatar(); int jointIndex = myAvatar->getJointIndex(joint); - return std::make_shared(myAvatar, jointIndex); + return std::make_shared(myAvatar, jointIndex); } } diff --git a/interface/src/ui/overlays/OverlayTransformNode.cpp b/interface/src/ui/overlays/OverlayTransformNode.cpp new file mode 100644 index 0000000000..64f5be932c --- /dev/null +++ b/interface/src/ui/overlays/OverlayTransformNode.cpp @@ -0,0 +1,30 @@ +// +// Created by Sabrina Shanman 9/5/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "OverlayTransformNode.h" + +OverlayTransformNode::OverlayTransformNode(std::weak_ptr overlay, int jointIndex) : + _overlay(overlay), + _jointIndex(jointIndex) +{} + +Transform OverlayTransformNode::getTransform() { + auto overlay = _overlay.lock(); + if (!overlay) { + return Transform(); + } + + bool success; + Transform jointWorldTransform = overlay->getTransform(_jointIndex, success); + if (!success) { + jointWorldTransform = Transform(); + } + + jointWorldTransform.setScale(overlay->getBounds().getScale()); + + return jointWorldTransform; +} \ No newline at end of file diff --git a/interface/src/ui/overlays/OverlayTransformNode.h b/interface/src/ui/overlays/OverlayTransformNode.h new file mode 100644 index 0000000000..79e6a37f45 --- /dev/null +++ b/interface/src/ui/overlays/OverlayTransformNode.h @@ -0,0 +1,26 @@ +// +// Created by Sabrina Shanman 9/5/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_OverlayTransformNode_h +#define hifi_OverlayTransformNode_h + +#include "NestableTransformNode.h" + +#include "Base3DOverlay.h" + +// For 3D overlays only +class OverlayTransformNode : public TransformNode { +public: + OverlayTransformNode(std::weak_ptr overlay, int jointIndex); + Transform getTransform() override; + +protected: + std::weak_ptr _overlay; + int _jointIndex; +}; + +#endif // hifi_OverlayTransformNode_h \ No newline at end of file diff --git a/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp new file mode 100644 index 0000000000..2f198e8917 --- /dev/null +++ b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp @@ -0,0 +1,30 @@ +// +// Created by Sabrina Shanman 9/5/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "AvatarTransformNode.h" + +AvatarTransformNode::AvatarTransformNode(AvatarWeakPointer avatar, int jointIndex) : + _avatar(avatar), + _jointIndex(jointIndex) +{} + +Transform AvatarTransformNode::getTransform() { + auto avatar = _avatar.lock(); + if (!avatar) { + return Transform(); + } + + bool success; + Transform jointWorldTransform = avatar->getTransform(_jointIndex, success); + if (!success) { + jointWorldTransform = Transform(); + } + + jointWorldTransform.setScale(avatar->scaleForChildren()); + + return jointWorldTransform; +} \ No newline at end of file diff --git a/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.h b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.h new file mode 100644 index 0000000000..9036d42639 --- /dev/null +++ b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.h @@ -0,0 +1,25 @@ +// +// Created by Sabrina Shanman 9/5/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_AvatarTransformNode_h +#define hifi_AvatarTransformNode_h + +#include "NestableTransformNode.h" + +#include "Avatar.h" + +class AvatarTransformNode : public TransformNode { +public: + AvatarTransformNode(AvatarWeakPointer avatar, int jointIndex); + Transform getTransform() override; + +protected: + AvatarWeakPointer _avatar; + int _jointIndex; +}; + +#endif // hifi_AvatarTransformNode_h \ No newline at end of file diff --git a/libraries/entities/src/EntityTransformNode.cpp b/libraries/entities/src/EntityTransformNode.cpp new file mode 100644 index 0000000000..42b60759db --- /dev/null +++ b/libraries/entities/src/EntityTransformNode.cpp @@ -0,0 +1,30 @@ +// +// Created by Sabrina Shanman 9/5/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "EntityTransformNode.h" + +EntityTransformNode::EntityTransformNode(EntityItemWeakPointer entity, int jointIndex) : + _entity(entity), + _jointIndex(jointIndex) +{} + +Transform EntityTransformNode::getTransform() { + auto entity = _entity.lock(); + if (!entity) { + return Transform(); + } + + bool success; + Transform jointWorldTransform = entity->getTransform(_jointIndex, success); + if (!success) { + jointWorldTransform = Transform(); + } + + jointWorldTransform.setScale(entity->getScaledDimensions()); + + return jointWorldTransform; +} \ No newline at end of file diff --git a/libraries/entities/src/EntityTransformNode.h b/libraries/entities/src/EntityTransformNode.h new file mode 100644 index 0000000000..fc5eeff652 --- /dev/null +++ b/libraries/entities/src/EntityTransformNode.h @@ -0,0 +1,25 @@ +// +// Created by Sabrina Shanman 9/5/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_EntityTransformNode_h +#define hifi_EntityTransformNode_h + +#include "NestableTransformNode.h" + +#include "EntityItem.h" + +class EntityTransformNode : public TransformNode { +public: + EntityTransformNode(EntityItemWeakPointer entity, int jointIndex); + Transform getTransform() override; + +protected: + EntityItemWeakPointer _entity; + int _jointIndex; +}; + +#endif // hifi_EntityTransformNode_h \ No newline at end of file From 84a2512f315d581edd54400ecb2cf457b69fc5b7 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 5 Sep 2018 16:50:39 -0700 Subject: [PATCH 269/744] Fix collision pick scale not being used --- interface/src/raypick/CollisionPick.cpp | 18 +++++++++--------- interface/src/raypick/CollisionPick.h | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 7d0276875b..d183aecfeb 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -86,23 +86,23 @@ bool CollisionPick::isLoaded() const { return !_mathPick.shouldComputeShapeInfo() || (_cachedResource && _cachedResource->isLoaded()); } -bool CollisionPick::getShapeInfoReady() { +bool CollisionPick::getShapeInfoReady(const CollisionRegion& pick) { if (_mathPick.shouldComputeShapeInfo()) { if (_cachedResource && _cachedResource->isLoaded()) { - computeShapeInfo(_mathPick, *_mathPick.shapeInfo, _cachedResource); + computeShapeInfo(pick, *_mathPick.shapeInfo, _cachedResource); _mathPick.loaded = true; } else { _mathPick.loaded = false; } } else { - computeShapeInfoDimensionsOnly(_mathPick, *_mathPick.shapeInfo, _cachedResource); + computeShapeInfoDimensionsOnly(pick, *_mathPick.shapeInfo, _cachedResource); _mathPick.loaded = true; } return _mathPick.loaded; } -void CollisionPick::computeShapeInfoDimensionsOnly(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { +void CollisionPick::computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { ShapeType type = shapeInfo.getType(); glm::vec3 dimensions = pick.transform.getScale(); QString modelURL = (resource ? resource->getURL().toString() : ""); @@ -115,7 +115,7 @@ void CollisionPick::computeShapeInfoDimensionsOnly(CollisionRegion& pick, ShapeI } } -void CollisionPick::computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { +void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { // This code was copied and modified from RenderableModelEntityItem::computeShapeInfo // TODO: Move to some shared code area (in entities-renderer? model-networking?) // after we verify this is working and do a diff comparison with RenderableModelEntityItem::computeShapeInfo @@ -393,9 +393,9 @@ PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pi // Cannot compute result return std::make_shared(pick.toVariantMap(), std::vector(), std::vector()); } - getShapeInfoReady(); + getShapeInfoReady(pick); - auto entityIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_ENTITIES, *pick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold); + auto entityIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_ENTITIES, *_mathPick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold); filterIntersections(entityIntersections); return std::make_shared(pick, entityIntersections, std::vector()); } @@ -409,9 +409,9 @@ PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pi // Cannot compute result return std::make_shared(pick, std::vector(), std::vector()); } - getShapeInfoReady(); + getShapeInfoReady(pick); - auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *pick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold); + auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *_mathPick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold); filterIntersections(avatarIntersections); return std::make_shared(pick, std::vector(), avatarIntersections); } diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index ce8b3bd199..fe0e5a6337 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -62,9 +62,9 @@ protected: // Returns true if the resource for _mathPick.shapeInfo is loaded or if a resource is not needed. bool isLoaded() const; // Returns true if _mathPick.shapeInfo is valid. Otherwise, attempts to get the _mathPick ready for use. - bool getShapeInfoReady(); - void computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); - void computeShapeInfoDimensionsOnly(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); + bool getShapeInfoReady(const CollisionRegion& pick); + void computeShapeInfo(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); + void computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); void filterIntersections(std::vector& intersections) const; CollisionRegion _mathPick; From abb632afda72c9b0d2ee966199e9f2d94726309c Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 5 Sep 2018 16:51:34 -0700 Subject: [PATCH 270/744] Update capsule when scale/load and teleport without safe landing --- interface/src/avatar/MyAvatar.cpp | 19 +++- interface/src/avatar/MyAvatar.h | 10 +- .../controllers/controllerModules/teleport.js | 100 ++++++++++++++---- 3 files changed, 108 insertions(+), 21 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 640c9821a0..4daa3f5eef 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -113,6 +113,7 @@ MyAvatar::MyAvatar(QThread* thread) : _recentModeReadings(MODE_READINGS_RING_BUFFER_SIZE), _bodySensorMatrix(), _goToPending(false), + _goToSafe(true), _goToPosition(), _goToOrientation(), _prevShouldDrawHead(true), @@ -509,7 +510,9 @@ void MyAvatar::update(float deltaTime) { if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { // When needed and ready, arrange to check and fix. _physicsSafetyPending = false; - safeLanding(_goToPosition); // no-op if already safe + if (_goToSafe) { + safeLanding(_goToPosition); // no-op if already safe + } } Head* head = getHead(); @@ -3007,7 +3010,7 @@ void MyAvatar::goToFeetLocation(const glm::vec3& newPosition, void MyAvatar::goToLocation(const glm::vec3& newPosition, bool hasOrientation, const glm::quat& newOrientation, - bool shouldFaceLocation) { + bool shouldFaceLocation, bool withSafeLanding) { // Most cases of going to a place or user go through this now. Some possible improvements to think about in the future: // - It would be nice if this used the same teleport steps and smoothing as in the teleport.js script, as long as it @@ -3027,6 +3030,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, _goToPending = true; _goToPosition = newPosition; + _goToSafe = withSafeLanding; _goToOrientation = getWorldOrientation(); if (hasOrientation) { qCDebug(interfaceapp).nospace() << "MyAvatar goToLocation - new orientation is " @@ -3299,6 +3303,17 @@ bool MyAvatar::getCollisionsEnabled() { return _characterController.computeCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS; } +QVariantMap MyAvatar::getCollisionCapsule() { + glm::vec3 start, end; + float radius; + getCapsule(start, end, radius); + QVariantMap capsule; + capsule["start"] = vec3toVariant(start); + capsule["end"] = vec3toVariant(end); + capsule["radius"] = QVariant(radius); + return capsule; +} + void MyAvatar::setCharacterControllerEnabled(bool enabled) { qCDebug(interfaceapp) << "MyAvatar.characterControllerEnabled is deprecated. Use MyAvatar.collisionsEnabled instead."; setCollisionsEnabled(enabled); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 8121b99e55..0f7ecedcad 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1017,6 +1017,12 @@ public: */ Q_INVOKABLE bool getCollisionsEnabled(); + /**jsdoc + * @function MyAvatar.getCollisionCapsule + * @returns {object} + */ + Q_INVOKABLE QVariantMap getCollisionCapsule(); + /**jsdoc * @function MyAvatar.setCharacterControllerEnabled * @param {boolean} enabled @@ -1178,11 +1184,12 @@ public slots: * @param {boolean} [hasOrientation=false] - Set to true to set the orientation of the avatar. * @param {Quat} [orientation=Quat.IDENTITY] - The new orientation for the avatar. * @param {boolean} [shouldFaceLocation=false] - Set to true to position the avatar a short distance away from + * @param {boolean} [withSafeLanding=true] - Set to false MyAvatar::safeLanding will not be called (used when teleporting). * the new position and orientate the avatar to face the position. */ void goToLocation(const glm::vec3& newPosition, bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(), - bool shouldFaceLocation = false); + bool shouldFaceLocation = false, bool withSafeLanding = true); /**jsdoc * @function MyAvatar.goToLocation * @param {object} properties @@ -1720,6 +1727,7 @@ private: bool _goToPending { false }; bool _physicsSafetyPending { false }; + bool _goToSafe { true }; glm::vec3 _goToPosition; glm::quat _goToOrientation; diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index c69382a47a..2dea25fc78 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -136,9 +136,7 @@ Script.include("/~/system/libraries/controllers.js"); this.state = TELEPORTER_STATES.IDLE; this.currentTarget = TARGET.INVALID; this.currentResult = null; - this.capsuleHeight = 2.0; - this.capsuleRadius = 0.25; - this.pickHeightOffset = 0.01; + this.capsuleThreshold = 0.05; this.getOtherModule = function() { var otherModule = this.hand === RIGHT_HAND ? leftTeleporter : rightTeleporter; @@ -176,18 +174,38 @@ Script.include("/~/system/libraries/controllers.js"); maxDistance: 8.0 }); - this.teleportCollisionPick = Picks.createPick(PickType.Collision, { - enabled: true, - filter: Picks.PICK_ENTITIES + Picks.PICK_AVATARS, - shape: { - shapeType: "capsule-y", - dimensions: { x: _this.capsuleRadius * 2.0, y: _this.capsuleHeight - (_this.capsuleRadius * 2.0), z: _this.capsuleRadius * 2.0 } - }, - position: { x: 0, y: _this.pickHeightOffset + (_this.capsuleHeight * 0.5), z: 0 }, - parentID: Pointers.getPointerProperties(_this.teleportParabolaHandInvisible).renderStates["invisible"].end, - threshold: 0.05 - }); - + this.teleportCollisionPick; + + this.recreateCollisionPick = function() { + if (_this.teleportCollisionPick !== undefined) { + Picks.removePick(_this.teleportCollisionPick); + } + + var capsuleData = MyAvatar.getCollisionCapsule(); + var capsuleHeight = Vec3.distance(capsuleData.start, capsuleData.end); + var offset = Vec3.distance(Vec3.sum(capsuleData.start, {x: 0, y: 0.5*capsuleHeight, z: 0}), MyAvatar.position); + var radius = capsuleData.radius; + var height = 2.0 * radius + capsuleHeight; + + _this.teleportCollisionPick = Picks.createPick(PickType.Collision, { + enabled: true, + parentID: Pointers.getPointerProperties(_this.teleportParabolaHandInvisible).renderStates["invisible"].end, + filter: Picks.PICK_ENTITIES + Picks.PICK_AVATARS, + shape: { + shapeType: "capsule-y", + dimensions: { + x: radius * 2.0, + y: height - (radius * 2.0), + z: radius * 2.0 + } + }, + position: { x: 0, y: offset + (height * 0.5), z: 0 }, + threshold: _this.capsuleThreshold + }); + } + + _this.recreateCollisionPick(); + this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Parabola, { joint: "Avatar", filter: Picks.PICK_ENTITIES, @@ -222,8 +240,6 @@ Script.include("/~/system/libraries/controllers.js"); Pointers.removePointer(_this.teleportParabolaHeadInvisible); Picks.removePick(_this.teleportCollisionPick); }; - - // Picks.setIgnoreItems(_this.teleportCollisionPick, []); this.axisButtonStateX = 0; // Left/right axis button pressed. this.axisButtonStateY = 0; // Up/down axis button pressed. @@ -355,7 +371,7 @@ Script.include("/~/system/libraries/controllers.js"); } else if (target === TARGET.SURFACE) { var offset = getAvatarFootOffset(); result.intersection.y += offset; - MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false); + MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false, false); HMD.centerUI(); MyAvatar.centerBody(); } @@ -558,7 +574,55 @@ Script.include("/~/system/libraries/controllers.js"); } } }; + + // This class execute a function after the param value haven't been set for a certain time + // It's used in this case to recreate the collision picks once when the avatar scale process ends + + var AfterSet = function(time, maxsteps, callback) { + var self = this; + this.time = time; + this.callback = callback; + this.init = false; + this.value = 0; + this.interval; + this.maxsteps = maxsteps; + this.steps = 0; + this.restateValue = function(value) { + if (self.steps++ > self.maxsteps) { + self.callback.call(this, self.value); + self.steps = 0; + } + if (!self.init) { + console.log("Starting apply after"); + } + self.init = true; + self.value = value; + if (self.interval !== undefined) { + Script.clearInterval(self.interval); + } + self.interval = Script.setInterval(function() { + self.callback.call(this, self.value); + self.init = false; + Script.clearInterval(self.interval); + self.interval = undefined; + }, self.time); + } + } + + var afterSet = new AfterSet(100, 30, function(value) { + leftTeleporter.recreateCollisionPick(); + rightTeleporter.recreateCollisionPick(); + }); + MyAvatar.onLoadComplete.connect(function () { + leftTeleporter.recreateCollisionPick(); + rightTeleporter.recreateCollisionPick(); + }); + + MyAvatar.sensorToWorldScaleChanged.connect(function() { + afterSet.restateValue(MyAvatar.getSensorToWorldScale()); + }); + Messages.subscribe('Hifi-Teleport-Disabler'); Messages.subscribe('Hifi-Teleport-Ignore-Add'); Messages.subscribe('Hifi-Teleport-Ignore-Remove'); From cde2dc2eaa4ffe696c80682b632617527eb1fd5c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 5 Sep 2018 17:04:05 -0700 Subject: [PATCH 271/744] please work --- libraries/render-utils/src/CauterizedModel.cpp | 4 ++-- libraries/render-utils/src/CauterizedModel.h | 2 +- libraries/render-utils/src/Model.cpp | 11 +++++++---- libraries/render-utils/src/Model.h | 7 +++++-- libraries/render-utils/src/SoftAttachmentModel.cpp | 4 ++-- libraries/render-utils/src/SoftAttachmentModel.h | 2 +- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 2754697db7..c4631c3676 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -102,7 +102,7 @@ void CauterizedModel::createRenderItemSet() { } } -void CauterizedModel::updateClusterMatrices(bool triggerBlendshapes) { +void CauterizedModel::updateClusterMatrices() { PerformanceTimer perfTimer("CauterizedModel::updateClusterMatrices"); if (!_needsUpdateClusterMatrices || !isLoaded()) { @@ -175,7 +175,7 @@ void CauterizedModel::updateClusterMatrices(bool triggerBlendshapes) { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); - if (triggerBlendshapes && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (_blendedVertexBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } diff --git a/libraries/render-utils/src/CauterizedModel.h b/libraries/render-utils/src/CauterizedModel.h index 12cf921e5b..36a96fb006 100644 --- a/libraries/render-utils/src/CauterizedModel.h +++ b/libraries/render-utils/src/CauterizedModel.h @@ -33,7 +33,7 @@ public: void createRenderItemSet() override; - virtual void updateClusterMatrices(bool triggerBlendshapes = true) override; + virtual void updateClusterMatrices() override; void updateRenderItems() override; const Model::MeshState& getCauterizeMeshState(int index) const; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 8287b9cca6..ba2bd28852 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -976,7 +976,7 @@ bool Model::addToScene(const render::ScenePointer& scene, render::Transaction& transaction, render::Item::Status::Getters& statusGetters) { if (!_addedToScene && isLoaded()) { - updateClusterMatrices(false); + updateClusterMatrices(); if (_modelMeshRenderItems.empty()) { createRenderItemSet(); } @@ -1307,6 +1307,7 @@ void Blender::run() { if (mesh.blendshapes.isEmpty()) { continue; } + vertices += mesh.vertices; normalsAndTangents += mesh.normalsAndTangents; glm::vec3* meshVertices = vertices.data() + offset; @@ -1486,7 +1487,7 @@ void Model::computeMeshPartLocalBounds() { } // virtual -void Model::updateClusterMatrices(bool triggerBlendshapes) { +void Model::updateClusterMatrices() { DETAILED_PERFORMANCE_TIMER("Model::updateClusterMatrices"); if (!_needsUpdateClusterMatrices || !isLoaded()) { @@ -1515,7 +1516,7 @@ void Model::updateClusterMatrices(bool triggerBlendshapes) { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); - if (triggerBlendshapes && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (_blendedVertexBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } @@ -1552,7 +1553,7 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo const auto vertexCount = mesh.vertices.size(); const auto verticesSize = vertexCount * sizeof(glm::vec3); const auto& buffer = _blendedVertexBuffers[i]; - assert(buffer); + assert(buffer && _blendedVertexBuffersInitialized); buffer->resize(mesh.vertices.size() * sizeof(glm::vec3) + mesh.normalsAndTangents.size() * sizeof(NormalType)); buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3)); buffer->setSubData(verticesSize, mesh.normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType)); @@ -1565,6 +1566,7 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo void Model::deleteGeometry() { _deleteGeometryCounter++; _blendedVertexBuffers.clear(); + _blendedVertexBuffersInitialized = false; _meshStates.clear(); _rig.destroyAnimGraph(); _blendedBlendshapeCoefficients.clear(); @@ -1630,6 +1632,7 @@ void Model::initializeBlendshapes(const FBXMesh& mesh, int index) { _blendedVertexBuffers[index]->setSubData(0, verticesSize, (const gpu::Byte*) mesh.vertices.constData()); _blendedVertexBuffers[index]->setSubData(verticesSize, normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); mesh.normalsAndTangents = normalsAndTangents; + _blendedVertexBuffersInitialized = true; } void Model::createRenderItemSet() { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 5ab50da162..e7534f5b89 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -159,7 +159,7 @@ public: bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } virtual void simulate(float deltaTime, bool fullUpdate = true); - virtual void updateClusterMatrices(bool triggerBlendshapes = true); + virtual void updateClusterMatrices(); /// Returns a reference to the shared geometry. const Geometry::Pointer& getGeometry() const { return _renderGeometry; } @@ -345,6 +345,8 @@ public: void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); + bool areBlendedVertexBuffersInitialized(int index) { return _blendedVertexBuffersInitialized; } + public slots: void loadURLFinished(bool success); @@ -424,8 +426,9 @@ protected: QUrl _url; std::unordered_map _blendedVertexBuffers; + bool _blendedVertexBuffersInitialized { false }; - QVector > > _dilatedTextures; + QVector>> _dilatedTextures; QVector _blendedBlendshapeCoefficients; int _blendNumber; diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index 93ce8f595a..b9a6581f1d 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -31,7 +31,7 @@ int SoftAttachmentModel::getJointIndexOverride(int i) const { // virtual // use the _rigOverride matrices instead of the Model::_rig -void SoftAttachmentModel::updateClusterMatrices(bool triggerBlendshapes) { +void SoftAttachmentModel::updateClusterMatrices() { if (!_needsUpdateClusterMatrices) { return; } @@ -78,7 +78,7 @@ void SoftAttachmentModel::updateClusterMatrices(bool triggerBlendshapes) { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); - if (triggerBlendshapes && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (_blendedVertexBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } diff --git a/libraries/render-utils/src/SoftAttachmentModel.h b/libraries/render-utils/src/SoftAttachmentModel.h index 03038c35f3..4335c1634e 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.h +++ b/libraries/render-utils/src/SoftAttachmentModel.h @@ -27,7 +27,7 @@ public: ~SoftAttachmentModel(); void updateRig(float deltaTime, glm::mat4 parentTransform) override; - void updateClusterMatrices(bool triggerBlendshapes = true) override; + void updateClusterMatrices() override; protected: int getJointIndexOverride(int i) const; From 901de120a694fef83871f48b5d07292da50dd88d Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Wed, 5 Sep 2018 17:20:49 -0700 Subject: [PATCH 272/744] Removing flavortext --- .../resources/qml/LoginDialog/LinkAccountBody.qml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index f6ad5385c0..423f6cc73b 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -86,21 +86,6 @@ Item { width: 48 height: 48 } - - FlavorText { - id: flavorTextContainer - anchors { - top: parent.top - margins: 0 - topMargin: hifi.dimensions.contentSpacing.y - } - - text: qsTr("Sign in to High Fidelity to make friends, get HFC, and buy interesting things on the Marketplace!") - wrapMode: Text.WordWrap - lineHeight: 2 - lineHeightMode: Text.ProportionalHeight - horizontalAlignment: Text.AlignHCenter - } ShortcutText { id: mainTextContainer From d2650f7ede3cfafa2130d51016b30412d1c10f29 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 5 Sep 2018 17:22:07 -0700 Subject: [PATCH 273/744] Modified priority function from Andrew --- .../src/avatars/AvatarMixerSlave.cpp | 6 +- libraries/shared/src/PrioritySortUtil.h | 64 +++---------------- 2 files changed, 11 insertions(+), 59 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index a61f65ffb0..9e38f465d3 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -313,9 +313,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // prepare to sort const auto& cameraViews = nodeData->getViewFrustums(); PrioritySortUtil::PriorityQueue sortedAvatars(cameraViews, - AvatarData::_avatarSortCoefficientSize, - AvatarData::_avatarSortCoefficientCenter, - AvatarData::_avatarSortCoefficientAge); + /*AvatarData::_avatarSortCoefficientSize*/ 1.0f, // Suggested weights from Andrew M. + /*AvatarData::_avatarSortCoefficientCenter*/ 0.5f, + /*AvatarData::_avatarSortCoefficientAge*/ 0.25f); sortedAvatars.reserve(_end - _begin); for (auto listedNode = _begin; listedNode != _end; ++listedNode) { diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 0a9bb45d46..ac2e6a6852 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -16,49 +16,7 @@ #include "NumericalConstants.h" #include "shared/ConicalViewFrustum.h" -/* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use: - -(1) Derive a class from pure-virtual PrioritySortUtil::Sortable that wraps a copy of - the Thing you want to prioritize and sort: - - class SortableWrapper: public PrioritySortUtil::Sortable { - public: - SortableWrapper(const Thing& thing) : _thing(thing) { } - glm::vec3 getPosition() const override { return _thing->getPosition(); } - float getRadius() const override { return 0.5f * _thing->getBoundingRadius(); } - uint64_t getTimestamp() const override { return _thing->getLastTime(); } - Thing getThing() const { return _thing; } - private: - Thing _thing; - }; - -(2) Make a PrioritySortUtil::PriorityQueue and add them to the queue: - - PrioritySortUtil::PriorityQueue sortedThings(viewFrustum); - std::priority_queue< PrioritySortUtil::Sortable > sortedThings; - for (thing in things) { - sortedThings.push(SortableWrapper(thing)); - } - -(3) Loop over your priority queue and do timeboxed work: - - NOTE: Be careful using references to members of instances of T from std::priority_queue. - Under the hood std::priority_queue may re-use instances of T. - For example, after a pop() or a push() the top T may have the same memory address - as the top T before the pop() or push() (but point to a swapped instance of T). - This causes a reference to member variable of T to point to a different value - when operations taken on std::priority_queue shuffle around the instances of T. - - uint64_t cutoffTime = usecTimestampNow() + TIME_BUDGET; - while (!sortedThings.empty()) { - const Thing& thing = sortedThings.top(); - // ...do work on thing... - sortedThings.pop(); - if (usecTimestampNow() > cutoffTime) { - break; - } - } -*/ +// PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. namespace PrioritySortUtil { @@ -84,9 +42,9 @@ namespace PrioritySortUtil { PriorityQueue() = delete; PriorityQueue(const ConicalViewFrustums& views) : _views(views) { } PriorityQueue(const ConicalViewFrustums& views, float angularWeight, float centerWeight, float ageWeight) - : _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) - , _usecCurrentTime(usecTimestampNow()) - { } + : _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) + , _usecCurrentTime(usecTimestampNow()) { + } void setViews(const ConicalViewFrustums& views) { _views = views; } @@ -138,14 +96,9 @@ namespace PrioritySortUtil { float cosineAngle = (glm::dot(offset, view.getDirection()) / distance); float age = (float)(_usecCurrentTime - thing.getTimestamp()); - // we modulate "age" drift rate by the cosineAngle term to make peripheral objects sort forward - // at a reduced rate but we don't want the "age" term to go zero or negative so we clamp it - const float MIN_COSINE_ANGLE_FACTOR = 0.1f; - float cosineAngleFactor = glm::max(cosineAngle, MIN_COSINE_ANGLE_FACTOR); - - float priority = _angularWeight * radius / distance - + _centerWeight * cosineAngle - + _ageWeight * cosineAngleFactor * age; + // the "age" term accumulates at the sum of all weights + float angularSize = glm::max(radius, MIN_RADIUS) / distance; + float priority = (_angularWeight * angularSize + _centerWeight * cosineAngle) * (age + 1.0f) + _ageWeight * age; // decrement priority of things outside keyhole if (distance - radius > view.getRadius()) { @@ -166,9 +119,8 @@ namespace PrioritySortUtil { }; } // namespace PrioritySortUtil -// for now we're keeping hard-coded sorted time budgets in one spot + // for now we're keeping hard-coded sorted time budgets in one spot const uint64_t MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2000; // usec const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1000; // usec #endif // hifi_PrioritySortUtil_h - From adf0a9a4147df64c59d325be68a7834aa1cc7493 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 5 Sep 2018 17:41:00 -0700 Subject: [PATCH 274/744] Convert priority age from microseconds to seconds --- libraries/shared/src/PrioritySortUtil.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index ac2e6a6852..075de13d9d 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -22,7 +22,7 @@ namespace PrioritySortUtil { constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; constexpr float DEFAULT_CENTER_COEF { 0.5f }; - constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) }; + constexpr float DEFAULT_AGE_COEF { 0.25f }; class Sortable { public: @@ -94,7 +94,7 @@ namespace PrioritySortUtil { float radius = glm::max(thing.getRadius(), MIN_RADIUS); // Other item's angle from view centre: float cosineAngle = (glm::dot(offset, view.getDirection()) / distance); - float age = (float)(_usecCurrentTime - thing.getTimestamp()); + float age = float((_usecCurrentTime - thing.getTimestamp()) / USECS_PER_SECOND); // the "age" term accumulates at the sum of all weights float angularSize = glm::max(radius, MIN_RADIUS) / distance; From 21f65d8a78d8dbba1608797a9d758bc1f93556f5 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Wed, 5 Sep 2018 17:50:06 -0700 Subject: [PATCH 275/744] position are used correctly for evaluting bounds in the Mesh --- libraries/fbx/src/FBXReader_Mesh.cpp | 4 ++-- libraries/graphics/src/graphics/Geometry.cpp | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index bb95fb42f7..7b83e74ebc 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -781,7 +781,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { attribChannel = 2; totalAttribBufferSize = totalVertsSize - positionsSize - normalsAndTangentsSize; - } else { + } /*else { auto posBuffer = std::make_shared(); posBuffer->setData(positionsSize, (const gpu::Byte*) vertBuffer->getData() + positionsOffset); vertexBufferStream->addBuffer(posBuffer, 0, positionElement.getSize()); @@ -794,7 +794,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { attribChannel = 1; totalAttribBufferSize = totalVertsSize - positionsSize; - } + }*/ // Define the vertex format, compute the offset for each attributes as we append them to the vertex format gpu::Offset bufOffset = 0; diff --git a/libraries/graphics/src/graphics/Geometry.cpp b/libraries/graphics/src/graphics/Geometry.cpp index 2fbe3708fd..f71dcbe27d 100755 --- a/libraries/graphics/src/graphics/Geometry.cpp +++ b/libraries/graphics/src/graphics/Geometry.cpp @@ -120,7 +120,8 @@ Box Mesh::evalPartBound(int partNum) const { for (;index != endIndex; index++) { // skip primitive restart indices if ((*index) != PRIMITIVE_RESTART_INDEX) { - box += vertices[(*index)]; + // box += vertices[(*index)]; + box += _vertexBuffer.get(part._baseVertex + (*index)); } } } @@ -137,11 +138,12 @@ Box Mesh::evalPartsBound(int partStart, int partEnd) const { Box partBound; auto index = _indexBuffer.cbegin() + (*part)._startIndex; auto endIndex = index + (*part)._numIndices; - auto vertices = &_vertexBuffer.get((*part)._baseVertex); + //auto vertices = &_vertexBuffer.get((*part)._baseVertex); for (;index != endIndex; index++) { // skip primitive restart indices if ((*index) != (uint) PRIMITIVE_RESTART_INDEX) { - partBound += vertices[(*index)]; + //partBound += vertices[(*index)]; + partBound += _vertexBuffer.get((*part)._baseVertex + (*index)); } } From 6fb335429de5b9db88c76d5d825e5d4dfb8530c6 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 5 Sep 2018 17:55:27 -0700 Subject: [PATCH 276/744] starting to re-implement the walking state detection for smoothwalking --- interface/src/avatar/MyAvatar.cpp | 15 +++++++++++++-- interface/src/avatar/MyAvatar.h | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4fa41809d0..8a29d3f9c7 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -516,6 +516,10 @@ void MyAvatar::update(float deltaTime) { head->relax(deltaTime); updateFromTrackers(deltaTime); + if (_isInWalkingState && glm::length(getControllerPoseInAvatarFrame(controller::Action::HEAD).getVelocity()) < 0.15f) { + _isInWalkingState = false; + } + // Get audio loudness data from audio input device // Also get the AudioClient so we can update the avatar bounding box data // on the AudioClient side. @@ -3912,7 +3916,7 @@ void MyAvatar::lateUpdatePalms() { Avatar::updatePalms(); } -static const float FOLLOW_TIME = 0.1f; +static const float FOLLOW_TIME = 0.5f; MyAvatar::FollowHelper::FollowHelper() { deactivate(); @@ -4002,7 +4006,8 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); - + qCDebug(interfaceapp) << "currentVelocity is " < 0.15f) { + myAvatar._isInWalkingState = true; + } } else { glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); @@ -4024,6 +4032,9 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons myAvatar.setResetMode(true); qCDebug(interfaceapp) << "failsafe called, default back " << anatomicalHeadToHipsDistance << " scale " << myAvatar.getAvatarScale() << " current length " << glm::length(currentHeadPosition - defaultHipsPosition); stepDetected = true; + if (glm::length(currentHeadPose.velocity) > 0.15f) { + myAvatar._isInWalkingState = true; + } } qCDebug(interfaceapp) << "current head height " << currentHeadPose.getTranslation().y << " scale " << myAvatar.getAvatarScale() << " anatomical " << anatomicalHeadToHipsDistance; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 06267b3819..aa76e29b14 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1788,6 +1788,7 @@ private: ThreadSafeValueCache _walkBackwardSpeed { DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED }; ThreadSafeValueCache _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; + bool _isInWalkingState { false }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; From 3a3ffcc98c792736ef7bfa0495606a11e3c99f7d Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 5 Sep 2018 18:14:06 -0700 Subject: [PATCH 277/744] First pass at improving anim stats. --- interface/resources/qml/Stats.qml | 61 ++++++++++-- interface/src/ui/Stats.cpp | 92 ++++++++++++++++++- interface/src/ui/Stats.h | 25 +++-- libraries/animation/src/AnimBlendLinear.cpp | 10 +- .../animation/src/AnimBlendLinearMove.cpp | 18 +--- libraries/animation/src/AnimContext.h | 42 +++++++++ libraries/animation/src/AnimNode.cpp | 4 - libraries/animation/src/AnimNode.h | 18 +--- libraries/animation/src/AnimStateMachine.cpp | 25 +---- libraries/animation/src/AnimStateMachine.h | 1 - libraries/animation/src/AnimVariant.cpp | 41 +++++++++ libraries/animation/src/AnimVariant.h | 3 + libraries/animation/src/Rig.cpp | 2 + libraries/animation/src/Rig.h | 7 +- 14 files changed, 264 insertions(+), 85 deletions(-) diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index d5cfcff1e2..26682d3db8 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -193,18 +193,63 @@ Item { text: "Yaw: " + root.yaw.toFixed(1) } StatText { - visible: root.animStackNames.length > 0; - text: "Anim Stack Names:" + visible: root.animAlphaValues.length > 0; + text: "Anim Alpha Values:" } ListView { width: geoCol.width - height: root.animStackNames.length * 15 - visible: root.animStackNames.length > 0; - model: root.animStackNames + height: root.animAlphaValues.length * 15 + visible: root.animAlphaValues.length > 0; + model: root.animAlphaValues delegate: StatText { - text: modelData.length > 30 - ? modelData.substring(0, 5) + "..." + modelData.substring(modelData.length - 22) - : modelData + text: { + var actualText = modelData.split("|")[1]; + if (actualText) { + if (actualText.length > 30) { + return actualText.substring(0, 5) + "..." + actualText.substring(actualText.length - 22); + } else { + return actualText; + } + } else { + return modelData; + } + } + color: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(0.3 * grayScale + 0.7, + 0.3 * grayScale + 0.7, + 0.3 * grayScale + 0.7, 1.0); + } + } + } + StatText { + visible: root.animVars.length > 0; + text: "AnimVars:" + } + ListView { + width: geoCol.width + height: root.animVars.length * 15 + visible: root.animVars.length > 0; + model: root.animVars + delegate: StatText { + text: { + var actualText = modelData.split("|")[1]; + if (actualText) { + if (actualText.length > 30) { + return actualText.substring(0, 5) + "..." + actualText.substring(actualText.length - 22); + } else { + return actualText; + } + } else { + return modelData; + } + } + color: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(0.3 * grayScale + 0.7, + 0.3 * grayScale + 0.7, + 0.3 * grayScale + 0.7, 1.0); + } } } StatText { diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 1b26c9b621..62e44645b9 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -207,13 +207,94 @@ void Stats::updateStats(bool force) { // Third column, avatar stats auto myAvatar = avatarManager->getMyAvatar(); - auto animStack = myAvatar->getSkeletonModel()->getRig().getAnimStack(); + auto debugAlphaMap = myAvatar->getSkeletonModel()->getRig().getDebugAlphaMap(); - _animStackNames.clear(); - for (auto animStackIterator = animStack.begin(); animStackIterator != animStack.end(); ++animStackIterator) { - _animStackNames << animStackIterator->first + ": " + QString::number(animStackIterator->second,'f',3); + // update animation debug alpha values + QStringList newAnimAlphaValues; + qint64 now = usecTimestampNow(); + for (auto& iter : debugAlphaMap) { + QString key = iter.first; + float alpha = std::get<0>(iter.second); + + auto prevIter = _prevDebugAlphaMap.find(key); + if (prevIter != _prevDebugAlphaMap.end()) { + float prevAlpha = std::get<0>(iter.second); + if (prevAlpha != alpha) { + // change detected: reset timer + _animAlphaValueChangedTimers[key] = now; + } + } else { + // new value: start timer + _animAlphaValueChangedTimers[key] = now; + } + + AnimNodeType type = std::get<1>(iter.second); + if (type == AnimNodeType::Clip) { + + // figure out the grayScale color of this line. + const float LIT_TIME = 2.0f; + const float FADE_OUT_TIME = 1.0f; + float grayScale = 0.0f; + float secondsElapsed = (float)(now - _animAlphaValueChangedTimers[key]) / (float)USECS_PER_SECOND; + if (secondsElapsed < LIT_TIME) { + grayScale = 1.0f; + } else if (secondsElapsed < LIT_TIME + FADE_OUT_TIME) { + grayScale = (LIT_TIME - (secondsElapsed - FADE_OUT_TIME)) / LIT_TIME; + } else { + grayScale = 0.0f; + } + + if (grayScale > 0.0f) { + // append grayScaleColor to start of debug string + newAnimAlphaValues << QString::number(grayScale, 'f', 2) + "|" + key + ": " + QString::number(alpha, 'f', 3); + } + } } - emit animStackNamesChanged(); + + _animAlphaValues = newAnimAlphaValues; + _prevDebugAlphaMap = debugAlphaMap; + + emit animAlphaValuesChanged(); + + // update animation anim vars + _animVarsList.clear(); + auto animVars = myAvatar->getSkeletonModel()->getRig().getAnimVars().toDebugMap(); + for (auto& iter : animVars) { + QString key = iter.first; + QString value = iter.second; + + auto prevIter = _prevAnimVars.find(key); + if (prevIter != _prevAnimVars.end()) { + QString prevValue = prevIter->second; + if (value != prevValue) { + // change detected: reset timer + _animVarChangedTimers[key] = now; + } + } else { + // new value: start timer + _animVarChangedTimers[key] = now; + } + + // figure out the grayScale color of this line. + const float LIT_TIME = 2.0f; + const float FADE_OUT_TIME = 0.5f; + float grayScale = 0.0f; + float secondsElapsed = (float)(now - _animVarChangedTimers[key]) / (float)USECS_PER_SECOND; + if (secondsElapsed < LIT_TIME) { + grayScale = 1.0f; + } else if (secondsElapsed < LIT_TIME + FADE_OUT_TIME) { + grayScale = (LIT_TIME - (secondsElapsed - FADE_OUT_TIME)) / LIT_TIME; + } else { + grayScale = 0.0f; + } + + if (grayScale > 0.0f) { + // append grayScaleColor to start of debug string + _animVarsList << QString::number(grayScale, 'f', 2) + "|" + key + ": " + value; + } + } + _prevAnimVars = animVars; + emit animVarsChanged(); glm::vec3 avatarPos = myAvatar->getWorldPosition(); STAT_UPDATE(position, QVector3D(avatarPos.x, avatarPos.y, avatarPos.z)); @@ -347,6 +428,7 @@ void Stats::updateStats(bool force) { } sendingModeStream << "] " << serverCount << " servers"; if (movingServerCount > 0) { + sendingModeStream << " "; } else { sendingModeStream << " "; diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index a0453a09c5..3e71c75da1 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -14,6 +14,7 @@ #include #include #include +#include #define STATS_PROPERTY(type, name, initialValue) \ Q_PROPERTY(type name READ name NOTIFY name##Changed) \ @@ -134,7 +135,6 @@ private: \ * @property {number} batchFrameTime - Read-only. * @property {number} engineFrameTime - Read-only. * @property {number} avatarSimulationTime - Read-only. - * @property {string[]} animStackNames - Read-only. * * * @property {number} x @@ -292,7 +292,8 @@ class Stats : public QQuickItem { STATS_PROPERTY(float, batchFrameTime, 0) STATS_PROPERTY(float, engineFrameTime, 0) STATS_PROPERTY(float, avatarSimulationTime, 0) - Q_PROPERTY(QStringList animStackNames READ animStackNames NOTIFY animStackNamesChanged) + Q_PROPERTY(QStringList animAlphaValues READ animAlphaValues NOTIFY animAlphaValuesChanged) + Q_PROPERTY(QStringList animVars READ animVars NOTIFY animVarsChanged) STATS_PROPERTY(int, stylusPicksCount, 0) STATS_PROPERTY(int, rayPicksCount, 0) @@ -326,7 +327,8 @@ public: } QStringList downloadUrls () { return _downloadUrls; } - QStringList animStackNames() { return _animStackNames; } + QStringList animAlphaValues() { return _animAlphaValues; } + QStringList animVars() { return _animVarsList; } public slots: void forceUpdateStats() { updateStats(true); } @@ -1028,12 +1030,8 @@ signals: */ void avatarSimulationTimeChanged(); - /**jsdoc - * Triggered when the value of the animStackNames property changes. - * @function Stats.animStackNamesChanged - * @returns {Signal} - */ - void animStackNamesChanged(); + void animAlphaValuesChanged(); + void animVarsChanged(); /**jsdoc * Triggered when the value of the rectifiedTextureCount property changes. @@ -1336,7 +1334,14 @@ private: QString _monospaceFont; const AudioIOStats* _audioStats; QStringList _downloadUrls = QStringList(); - QStringList _animStackNames = QStringList(); + + QStringList _animAlphaValues = QStringList(); + AnimContext::DebugAlphaMap _prevDebugAlphaMap; // alpha values from previous frame + std::map _animAlphaValueChangedTimers; // last time alpha value has changed + + QStringList _animVarsList = QStringList(); + std::map _prevAnimVars; // anim vars from previous frame + std::map _animVarChangedTimers; // last time animVar value has changed. }; #endif // hifi_Stats_h diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index e2d79f864d..17ca88123f 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -27,7 +27,7 @@ AnimBlendLinear::~AnimBlendLinear() { const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { _alpha = animVars.lookup(_alphaVar, _alpha); - float parentAlpha = _animStack[_id]; + float parentDebugAlpha = context.getDebugAlpha(_id); if (_children.size() == 0) { for (auto&& pose : _poses) { @@ -35,7 +35,7 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, con } } else if (_children.size() == 1) { _poses = _children[0]->evaluate(animVars, context, dt, triggersOut); - _animStack[_children[0]->getID()] = parentAlpha; + context.setDebugAlpha(_children[0]->getID(), parentDebugAlpha, _children[0]->getType()); } else { float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); size_t prevPoseIndex = glm::floor(clampedAlpha); @@ -48,12 +48,12 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, con float weight2 = 0.0f; if (prevPoseIndex == nextPoseIndex) { weight2 = 1.0f; - _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + context.setDebugAlpha(_children[nextPoseIndex]->getID(), weight2 * parentDebugAlpha, _children[nextPoseIndex]->getType()); } else { weight2 = alpha; weight1 = 1.0f - weight2; - _animStack[_children[prevPoseIndex]->getID()] = weight1 * parentAlpha; - _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + context.setDebugAlpha(_children[prevPoseIndex]->getID(), weight1 * parentDebugAlpha, _children[prevPoseIndex]->getType()); + context.setDebugAlpha(_children[nextPoseIndex]->getID(), weight2 * parentDebugAlpha, _children[nextPoseIndex]->getType()); } } processOutputJoints(triggersOut); diff --git a/libraries/animation/src/AnimBlendLinearMove.cpp b/libraries/animation/src/AnimBlendLinearMove.cpp index 42098eb072..07e1c17f77 100644 --- a/libraries/animation/src/AnimBlendLinearMove.cpp +++ b/libraries/animation/src/AnimBlendLinearMove.cpp @@ -62,9 +62,7 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, speed = animVars.lookup("moveForwardSpeed", speed); } _alpha = calculateAlpha(speed, _characteristicSpeeds); - float parentAlpha = _animStack[_id]; - - _animStack["speed"] = speed; + float parentDebugAlpha = context.getDebugAlpha(_id); if (_children.size() == 0) { for (auto&& pose : _poses) { @@ -77,7 +75,7 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, float prevDeltaTime, nextDeltaTime; setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); - _animStack[_children[0]->getID()] = parentAlpha; + context.setDebugAlpha(_children[0]->getID(), parentDebugAlpha, _children[0]->getType()); } else { auto clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); auto prevPoseIndex = glm::floor(clampedAlpha); @@ -87,17 +85,11 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); - // weights are for animation stack debug purposes only. - float weight1 = 0.0f; - float weight2 = 0.0f; if (prevPoseIndex == nextPoseIndex) { - weight2 = 1.0f; - _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + context.setDebugAlpha(_children[nextPoseIndex]->getID(), parentDebugAlpha, _children[nextPoseIndex]->getType()); } else { - weight2 = alpha; - weight1 = 1.0f - weight2; - _animStack[_children[prevPoseIndex]->getID()] = weight1 * parentAlpha; - _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + context.setDebugAlpha(_children[prevPoseIndex]->getID(), (1.0f - alpha) * parentDebugAlpha, _children[prevPoseIndex]->getType()); + context.setDebugAlpha(_children[nextPoseIndex]->getID(), alpha * parentDebugAlpha, _children[nextPoseIndex]->getType()); } } diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index e8bf5c34eb..ecfb94a28e 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -14,8 +14,27 @@ #include #include +#include +#include +#include + +enum class AnimNodeType { + Clip = 0, + BlendLinear, + BlendLinearMove, + Overlay, + StateMachine, + Manipulator, + InverseKinematics, + DefaultPose, + TwoBoneIK, + PoleVectorConstraint, + NumTypes +}; + class AnimContext { public: + AnimContext() {} AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains, const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix); @@ -25,6 +44,26 @@ public: const glm::mat4& getGeometryToRigMatrix() const { return _geometryToRigMatrix; } const glm::mat4& getRigToWorldMatrix() const { return _rigToWorldMatrix; } + float getDebugAlpha(const QString& key) const { + auto it = _debugAlphaMap.find(key); + if (it != _debugAlphaMap.end()) { + return std::get<0>(it->second); + } else { + return 1.0f; + } + } + + using DebugAlphaMapValue = std::tuple; + using DebugAlphaMap = std::map; + + void setDebugAlpha(const QString& key, float alpha, AnimNodeType type) const { + _debugAlphaMap[key] = DebugAlphaMapValue(alpha, type); + } + + const DebugAlphaMap& getDebugAlphaMap() const { + return _debugAlphaMap; + } + protected: bool _enableDebugDrawIKTargets { false }; @@ -32,6 +71,9 @@ protected: bool _enableDebugDrawIKChains { false }; glm::mat4 _geometryToRigMatrix; glm::mat4 _rigToWorldMatrix; + + // used for debugging internal state of animation system. + mutable DebugAlphaMap _debugAlphaMap; }; #endif // hifi_AnimContext_h diff --git a/libraries/animation/src/AnimNode.cpp b/libraries/animation/src/AnimNode.cpp index a8e76617ae..f055e6b473 100644 --- a/libraries/animation/src/AnimNode.cpp +++ b/libraries/animation/src/AnimNode.cpp @@ -12,10 +12,6 @@ #include -std::map AnimNode::_animStack = { - {"none", 0.0f} -}; - AnimNode::Pointer AnimNode::getParent() { return _parent.lock(); } diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 1f14889ce4..1a12bb8ddb 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -36,19 +36,7 @@ class QJsonObject; class AnimNode : public std::enable_shared_from_this { public: - enum class Type { - Clip = 0, - BlendLinear, - BlendLinearMove, - Overlay, - StateMachine, - Manipulator, - InverseKinematics, - DefaultPose, - TwoBoneIK, - PoleVectorConstraint, - NumTypes - }; + using Type = AnimNodeType; using Pointer = std::shared_ptr; using ConstPointer = std::shared_ptr; @@ -84,7 +72,6 @@ public: } void setCurrentFrame(float frame); - const std::map getAnimStack() { return _animStack; } template bool traverse(F func) { @@ -127,9 +114,6 @@ protected: std::weak_ptr _parent; std::vector _outputJointNames; - // global available to Stats.h - static std::map _animStack; - // no copies AnimNode(const AnimNode&) = delete; AnimNode& operator=(const AnimNode&) = delete; diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 90fd425ae5..00ae5e6d39 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -23,9 +23,7 @@ AnimStateMachine::~AnimStateMachine() { const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - if (_id.contains("userAnimStateMachine")) { - _animStack.clear(); - } + float parentDebugAlpha = context.getDebugAlpha(_id); QString desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID()); if (_currentState->getID() != desiredStateID) { @@ -33,8 +31,6 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co bool foundState = false; for (auto& state : _states) { if (state->getID() == desiredStateID) { - // parenthesis means previous state, which is a snapshot. - _previousStateID = "(" + _currentState->getID() + ")"; switchState(animVars, context, state); foundState = true; break; @@ -48,8 +44,6 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co // evaluate currentState transitions auto desiredState = evaluateTransitions(animVars); if (desiredState != _currentState) { - // parenthesis means previous state, which is a snapshot. - _previousStateID = "(" + _currentState->getID() + ")"; switchState(animVars, context, desiredState); } @@ -57,17 +51,8 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co auto currentStateNode = _children[_currentState->getChildIndex()]; assert(currentStateNode); - if (!_previousStateID.contains("none")) { - _animStack[_previousStateID] = 1.0f - _alpha; - } - if (_duringInterp) { _alpha += _alphaVel * dt; - if (_alpha > 1.0f) { - _animStack[_currentState->getID()] = 1.0f; - } else { - _animStack[_currentState->getID()] = _alpha; - } if (_alpha < 1.0f) { AnimPoseVec* nextPoses = nullptr; AnimPoseVec* prevPoses = nullptr; @@ -88,18 +73,16 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co if (_poses.size() > 0 && nextPoses && prevPoses && nextPoses->size() > 0 && prevPoses->size() > 0) { ::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), _alpha, &_poses[0]); } + context.setDebugAlpha(_currentState->getID(), _alpha * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); } else { _duringInterp = false; - if (_animStack.count(_previousStateID) > 0) { - _animStack.erase(_previousStateID); - } - _previousStateID = "none"; _prevPoses.clear(); _nextPoses.clear(); } } + if (!_duringInterp) { - _animStack[_currentState->getID()] = 1.0f; + context.setDebugAlpha(_currentState->getID(), parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); _poses = currentStateNode->evaluate(animVars, context, dt, triggersOut); } processOutputJoints(triggersOut); diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index b20e5203a1..8e1e1b7bd8 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -138,7 +138,6 @@ protected: float _alpha = 0.0f; AnimPoseVec _prevPoses; AnimPoseVec _nextPoses; - QString _previousStateID { "none" }; State::Pointer _currentState; std::vector _states; diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp index 483a7999c9..509462984a 100644 --- a/libraries/animation/src/AnimVariant.cpp +++ b/libraries/animation/src/AnimVariant.cpp @@ -67,6 +67,7 @@ QScriptValue AnimVariantMap::animVariantMapToScriptValue(QScriptEngine* engine, } return target; } + void AnimVariantMap::copyVariantsFrom(const AnimVariantMap& other) { for (auto& pair : other._map) { _map[pair.first] = pair.second; @@ -124,3 +125,43 @@ void AnimVariantMap::animVariantMapFromScriptValue(const QScriptValue& source) { } } } + +std::map AnimVariantMap::toDebugMap() const { + std::map result; + for (auto& pair : _map) { + switch (pair.second.getType()) { + case AnimVariant::Type::Bool: + result[pair.first] = QString("%1").arg(pair.second.getBool()); + break; + case AnimVariant::Type::Int: + result[pair.first] = QString("%1").arg(pair.second.getInt()); + break; + case AnimVariant::Type::Float: + result[pair.first] = QString::number(pair.second.getFloat(), 'f', 3); + break; + case AnimVariant::Type::Vec3: { + glm::vec3 value = pair.second.getVec3(); + result[pair.first] = QString("(%1, %2, %3)"). + arg(QString::number(value.x, 'f', 3)). + arg(QString::number(value.y, 'f', 3)). + arg(QString::number(value.z, 'f', 3)); + break; + } + case AnimVariant::Type::Quat: { + glm::quat value = pair.second.getQuat(); + result[pair.first] = QString("(%1, %2, %3, %4)"). + arg(QString::number(value.x, 'f', 3)). + arg(QString::number(value.y, 'f', 3)). + arg(QString::number(value.z, 'f', 3)). + arg(QString::number(value.w, 'f', 3)); + break; + } + case AnimVariant::Type::String: + result[pair.first] = pair.second.getString(); + break; + default: + assert(("invalid AnimVariant::Type", false)); + } + } + return result; +} diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index d383b5abb8..0f921984b1 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -235,6 +235,9 @@ public: void animVariantMapFromScriptValue(const QScriptValue& object); void copyVariantsFrom(const AnimVariantMap& other); + // For stat debugging. + std::map toDebugMap() const; + #ifdef NDEBUG void dump() const { qCDebug(animation) << "AnimVariantMap ="; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 13cf165dac..91d4e0f9d3 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1061,8 +1061,10 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons // animations haven't fully loaded yet. _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } + _lastAnimVars = _animVars; _animVars.clearTriggers(); _animVars = triggersOut; + _lastContext = context; } applyOverridePoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index accbcccbbc..95a4b6f40d 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -222,7 +222,9 @@ public: // input assumed to be in rig space void computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const; - const std::map getAnimStack() { return _animNode->getAnimStack(); } + // used to debug animation playback + const AnimContext::DebugAlphaMap& getDebugAlphaMap() const { return _lastContext.getDebugAlphaMap(); } + const AnimVariantMap& getAnimVars() const { return _lastAnimVars; } void toggleSmoothPoleVectors() { _smoothPoleVectors = !_smoothPoleVectors; }; signals: @@ -388,6 +390,9 @@ protected: int _rigId; bool _headEnabled { false }; + + AnimContext _lastContext; + AnimVariantMap _lastAnimVars; }; #endif /* defined(__hifi__Rig__) */ From 8420afd14344625b41b636b374f5d0e8c3fe249f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Sep 2018 14:00:33 +1200 Subject: [PATCH 278/744] Fix title not not going back to "Settings" upon Save or Cancel --- interface/resources/qml/hifi/tablet/TabletMenuStack.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index 4d36a57793..fe636dafa5 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -1,5 +1,5 @@ // -// MessageDialog.qml +// TabletMenuStack.qml // // Created by Dante Ruiz on 13 Feb 2017 // Copyright 2016 High Fidelity, Inc. @@ -66,7 +66,7 @@ Item { function popSource() { console.log("trying to pop page"); - d.pop(); + closeLastMenu(); } function toModel(items, newMenu) { From ab47e16be1f8bd14c2308b60868b411641d50759 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Sep 2018 14:35:08 +1200 Subject: [PATCH 279/744] Fix up titles and leading white space of some dev menu tablet dialogs --- .../resources/qml/hifi/dialogs/TabletDCDialog.qml | 3 ++- .../qml/hifi/dialogs/TabletEntityStatistics.qml | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml index 1ec7ea3e0e..afe06897df 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml @@ -18,6 +18,7 @@ import "../../windows" Rectangle { id: root objectName: "DCConectionTiming" + property string title: "Domain Connection Timing" signal sendToScript(var message); property bool isHMD: false @@ -33,7 +34,7 @@ Rectangle { Row { id: header anchors.top: parent.top - anchors.topMargin: hifi.dimensions.tabletMenuHeader + anchors.topMargin: hifi.dimensions.contentMargin.y anchors.leftMargin: 5 anchors.rightMargin: 5 anchors.left: parent.left diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml index 7bce460283..24798af21a 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml @@ -18,6 +18,7 @@ import "../../windows" Rectangle { id: root objectName: "EntityStatistics" + property string title: "Entity Statistics" signal sendToScript(var message); property bool isHMD: false @@ -40,6 +41,7 @@ Rectangle { id: scrollView width: parent.width anchors.top: parent.top + anchors.topMargin: hifi.dimensions.contentMargin.y anchors.bottom: parent.bottom anchors.bottomMargin: hifi.dimensions.tabletMenuHeader contentWidth: column.implicitWidth @@ -48,10 +50,15 @@ Rectangle { Column { id: column - anchors.margins: 10 + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - y: hifi.dimensions.tabletMenuHeader //-bgNavBar + anchors { + topMargin: 0 + leftMargin: 10 + rightMargin: 10 + bottomMargin: 0 + } spacing: 20 TabletEntityStatisticsItem { From 78fa52285f25ebbd68f3318b724fe4bd8c8f726f Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 5 Sep 2018 23:48:21 -0700 Subject: [PATCH 280/744] adding show password toggle in textfield --- interface/resources/images/eyeClosed.svg | 5 +++ interface/resources/images/eyeOpen.svg | 4 +++ .../qml/LoginDialog/LinkAccountBody.qml | 36 +++++++++++++++---- 3 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 interface/resources/images/eyeClosed.svg create mode 100644 interface/resources/images/eyeOpen.svg diff --git a/interface/resources/images/eyeClosed.svg b/interface/resources/images/eyeClosed.svg new file mode 100644 index 0000000000..6719471b3d --- /dev/null +++ b/interface/resources/images/eyeClosed.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/interface/resources/images/eyeOpen.svg b/interface/resources/images/eyeOpen.svg new file mode 100644 index 0000000000..ec5ceb5238 --- /dev/null +++ b/interface/resources/images/eyeOpen.svg @@ -0,0 +1,4 @@ + + + + diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 423f6cc73b..c40dff61ee 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -86,7 +86,7 @@ Item { width: 48 height: 48 } - + ShortcutText { id: mainTextContainer anchors { @@ -142,6 +142,7 @@ Item { onLinkActivated: loginDialog.openUrl(link) } + onFocusChanged: { root.text = ""; } @@ -151,7 +152,6 @@ Item { id: passwordField width: parent.width placeholderText: "Password" - echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password activeFocusOnPress: true ShortcutText { @@ -177,12 +177,34 @@ Item { root.isPassword = true; } - Keys.onReturnPressed: linkAccountBody.login() - } + Image { + id: showPasswordImage + x: parent.width - 40 + height: parent.height + width: parent.width - (parent.width - 40) + source: "../../images/eyeOpen.svg" + } - CheckBox { - id: showPassword - text: "Show password" + Rectangle { + z: 10 + x: parent.width - 40 + width: parent.width - (parent.width - 40) + height: parent.height + color: "transparent" + MouseArea { + id: passwordFieldMouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + property bool showPassword: false + onClicked: { + showPassword = !showPassword; + passwordField.echoMode = showPassword ? TextInput.Normal : TextInput.Password; + showPasswordImage.source = showPassword ? "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg"; + } + } + } + + Keys.onReturnPressed: linkAccountBody.login() } InfoItem { From 6d055ba1360e492183d5aa692af7182a415a018c Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 5 Sep 2018 23:49:49 -0700 Subject: [PATCH 281/744] changing sign up label --- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index c40dff61ee..aed34609e4 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -254,7 +254,7 @@ Item { RalewaySemiBold { size: hifi.fontSizes.inputLabel anchors.verticalCenter: parent.verticalCenter - text: qsTr("Don't have an account?") + text: qsTr("New to High Fidelity?") } Button { From 3f1eba418dd210c6435af80df009b460dcb5f185 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 6 Sep 2018 00:07:23 -0700 Subject: [PATCH 282/744] modifying location of links for user/pw --- .../qml/LoginDialog/LinkAccountBody.qml | 68 +++++++++---------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index aed34609e4..caf5ce0e53 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -116,7 +116,6 @@ Item { } spacing: 2 * hifi.dimensions.contentSpacing.y - TextField { id: usernameField text: Settings.getValue("wallet/savedUsername", ""); @@ -125,28 +124,25 @@ Item { placeholderText: "Username or Email" activeFocusOnPress: true - ShortcutText { - z: 10 - anchors { - left: usernameField.right - top: usernameField.bottom - leftMargin: usernameField.textFieldLabel.contentWidth + 10 - topMargin: -19 - } - - text: "Forgot Username?" - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - linkColor: hifi.colors.blueAccent - - onLinkActivated: loginDialog.openUrl(link) - } - onFocusChanged: { root.text = ""; } } + ShortcutText { + z: 10 + anchors { + leftMargin: usernameField.textFieldLabel.contentWidth + 10 + topMargin: -19 + } + + text: "Forgot Username?" + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignRight + linkColor: hifi.colors.blueAccent + + onLinkActivated: loginDialog.openUrl(link) + } TextField { id: passwordField @@ -154,24 +150,6 @@ Item { placeholderText: "Password" activeFocusOnPress: true - ShortcutText { - z: 10 - anchors { - left: passwordField.right - top: passwordField.bottom - leftMargin: passwordField.textFieldLabel.contentWidth + 10 - topMargin: -19 - } - - text: "Forgot Password?" - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - linkColor: hifi.colors.blueAccent - - onLinkActivated: loginDialog.openUrl(link) - } - onFocusChanged: { root.text = ""; root.isPassword = true; @@ -207,6 +185,22 @@ Item { Keys.onReturnPressed: linkAccountBody.login() } + ShortcutText { + z: 10 + anchors { + leftMargin: passwordField.textFieldLabel.contentWidth + 10 + topMargin: -19 + } + + text: "Forgot Password?" + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + linkColor: hifi.colors.blueAccent + + onLinkActivated: loginDialog.openUrl(link) + } + InfoItem { id: additionalInformation From 105a615745361c85c2d4b2f6a4e6e6d4e99445db Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 22 Aug 2018 14:09:20 -0700 Subject: [PATCH 283/744] Fix no-ui crashes --- interface/src/Application.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2fe67ec5cb..21b953b686 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2663,6 +2663,10 @@ Application::~Application() { void Application::initializeGL() { qCDebug(interfaceapp) << "Created Display Window."; +#ifdef DISABLE_QML + setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); +#endif + // initialize glut for shape drawing; Qt apparently initializes it on OS X if (_isGLInitialized) { return; @@ -6870,6 +6874,9 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { shortName = shortName.mid(startIndex, endIndex - startIndex); } +#ifdef DISABLE_QML + DependencyManager::get()->loadScript(scriptFilenameOrURL); +#else QString message = "Would you like to run this script:\n" + shortName; ModalDialogListener* dlg = OffscreenUi::asyncQuestion(getWindow(), "Run Script", message, QMessageBox::Yes | QMessageBox::No); @@ -6884,7 +6891,7 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { } QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr); }); - +#endif return true; } From b6247e08503260903ba4b8307d28db73f2517f10 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 23 Aug 2018 12:19:07 -0700 Subject: [PATCH 284/744] Remove cmake spam --- cmake/macros/AutoScribeShader.cmake | 1 - cmake/macros/TargetJson.cmake | 1 - 2 files changed, 2 deletions(-) diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index 1d1e5970d9..9c5ad70c78 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -9,7 +9,6 @@ # macro(AUTOSCRIBE_SHADER) - message(STATUS "Processing shader ${SHADER_FILE}") unset(SHADER_INCLUDE_FILES) # Grab include files foreach(includeFile ${ARGN}) diff --git a/cmake/macros/TargetJson.cmake b/cmake/macros/TargetJson.cmake index 057024cd0a..9262c2ce48 100644 --- a/cmake/macros/TargetJson.cmake +++ b/cmake/macros/TargetJson.cmake @@ -8,6 +8,5 @@ macro(TARGET_JSON) add_dependency_external_projects(json) find_package(JSON REQUIRED) - message("JSON_INCLUDE_DIRS ${JSON_INCLUDE_DIRS}") target_include_directories(${TARGET_NAME} PUBLIC ${JSON_INCLUDE_DIRS}) endmacro() \ No newline at end of file From 35d86dd7982291f096686a0912da30e3b3bfd444 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 23 Aug 2018 12:19:50 -0700 Subject: [PATCH 285/744] Fail unit test on non-compiling pipelines --- tests/shaders/src/ShaderTests.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/shaders/src/ShaderTests.cpp b/tests/shaders/src/ShaderTests.cpp index 4dd15710f9..03dc034cd0 100644 --- a/tests/shaders/src/ShaderTests.cpp +++ b/tests/shaders/src/ShaderTests.cpp @@ -230,8 +230,8 @@ void ShaderTests::testShaderLoad() { auto glBackend = std::static_pointer_cast(_gpuContext->getBackend()); auto glshader = gpu::gl::GLShader::sync(*glBackend, *program); if (!glshader) { - qDebug() << "Failed to compile or link vertex " << vertexId << " fragment " << fragmentId; - continue; + qWarning() << "Failed to compile or link vertex " << vertexId << " fragment " << fragmentId; + QFAIL("Program link error"); } QVERIFY(glshader != nullptr); @@ -301,7 +301,7 @@ void ShaderTests::testShaderLoad() { QFAIL(error.what()); } - for (uint32_t i = 0; i <= maxShader; ++i) { + for (uint32_t i = 1; i <= maxShader; ++i) { auto used = usedShaders.count(i); if (0 != usedShaders.count(i)) { continue; From ac84a498b391de3c5e683a6d95851bb66b2bcb8c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 23 Aug 2018 12:22:39 -0700 Subject: [PATCH 286/744] Cleanup dead declarations --- libraries/procedural/src/procedural/ShaderConstants.h | 1 - libraries/render-utils/src/DeferredLightingEffect.cpp | 1 - libraries/render-utils/src/GeometryCache.cpp | 2 -- libraries/render-utils/src/HighlightEffect.cpp | 1 - libraries/render-utils/src/render-utils/ShaderConstants.h | 5 ----- libraries/render-utils/src/render-utils/fxaa.slp | 1 + 6 files changed, 1 insertion(+), 10 deletions(-) create mode 100644 libraries/render-utils/src/render-utils/fxaa.slp diff --git a/libraries/procedural/src/procedural/ShaderConstants.h b/libraries/procedural/src/procedural/ShaderConstants.h index 1995996c97..bfbf2a2691 100644 --- a/libraries/procedural/src/procedural/ShaderConstants.h +++ b/libraries/procedural/src/procedural/ShaderConstants.h @@ -14,7 +14,6 @@ #ifndef PROCEDURAL_SHADER_CONSTANTS_H #define PROCEDURAL_SHADER_CONSTANTS_H -// Polyvox #define PROCEDURAL_UNIFORM_TIME 200 #define PROCEDURAL_UNIFORM_DATE 201 #define PROCEDURAL_UNIFORM_FRAME_COUNT 202 diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index af5a3f3e63..3b23711a64 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -32,7 +32,6 @@ namespace ru { using render_utils::slot::texture::Texture; using render_utils::slot::buffer::Buffer; - using render_utils::slot::uniform::Uniform; } namespace gr { diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index cbc9b40129..2fe811c97b 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -2104,9 +2104,7 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { auto stateNoBlend = std::make_shared(); PrepareStencil::testMaskDrawShape(*stateNoBlend); - auto noBlendPS = gpu::Shader::createVertex(shader::gpu::fragment::DrawTextureOpaque); auto programNoBlend = gpu::Shader::createProgram(shader::render_utils::program::standardDrawTextureNoBlend); - _standardDrawPipelineNoBlend = gpu::Pipeline::create(programNoBlend, stateNoBlend); }); diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 7e2e55c768..11326b1120 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -28,7 +28,6 @@ using namespace render; namespace ru { using render_utils::slot::texture::Texture; using render_utils::slot::buffer::Buffer; - using render_utils::slot::uniform::Uniform; } namespace gr { diff --git a/libraries/render-utils/src/render-utils/ShaderConstants.h b/libraries/render-utils/src/render-utils/ShaderConstants.h index 6a88a62287..ccf6314a39 100644 --- a/libraries/render-utils/src/render-utils/ShaderConstants.h +++ b/libraries/render-utils/src/render-utils/ShaderConstants.h @@ -43,9 +43,6 @@ #define RENDER_UTILS_BUFFER_AMBIENT_LIGHT 6 #define RENDER_UTILS_BUFFER_LIGHT_INDEX 7 -#define RENDER_UTILS_UNIFORM_LIGHT_RADIUS 0 -#define RENDER_UTILS_UNIFORM_LIGHT_TEXCOORD_TRANSFORM 1 - // Deferred lighting resolution #define RENDER_UTILS_TEXTURE_DEFERRRED_COLOR 0 #define RENDER_UTILS_TEXTURE_DEFERRRED_NORMAL 1 @@ -140,8 +137,6 @@ enum Uniform { TextOutline = RENDER_UTILS_UNIFORM_TEXT_OUTLINE, TaaSharpenIntensity = GPU_UNIFORM_EXTRA0, HighlightOutlineWidth = GPU_UNIFORM_EXTRA0, - LightRadius = RENDER_UTILS_UNIFORM_LIGHT_RADIUS, - TexcoordTransform = RENDER_UTILS_UNIFORM_LIGHT_TEXCOORD_TRANSFORM, }; } diff --git a/libraries/render-utils/src/render-utils/fxaa.slp b/libraries/render-utils/src/render-utils/fxaa.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/fxaa.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord From c8eb4da6b61e7f1e9da6008ef7068202049b47d5 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 23 Aug 2018 15:36:32 -0700 Subject: [PATCH 287/744] Remove simple draw task uniforms --- libraries/gpu/src/gpu/DrawColor.slf | 10 +++++++--- libraries/gpu/src/gpu/DrawColoredTexture.slf | 13 +++++++++---- libraries/render/src/render/DrawTask.cpp | 17 +++++++++++------ libraries/render/src/render/DrawTask.h | 3 ++- libraries/render/src/render/drawItemBounds.slv | 15 +++++++++++---- 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/libraries/gpu/src/gpu/DrawColor.slf b/libraries/gpu/src/gpu/DrawColor.slf index 2540d56d69..fdea26fa68 100644 --- a/libraries/gpu/src/gpu/DrawColor.slf +++ b/libraries/gpu/src/gpu/DrawColor.slf @@ -13,12 +13,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/ShaderConstants.h@> +struct DrawColorParams { + vec4 color; +}; -layout(location=GPU_UNIFORM_COLOR) uniform vec4 color; +layout(binding=0) uniform drawColorParamsBuffer { + DrawColorParams params; +}; layout(location=0) out vec4 outFragColor; void main(void) { - outFragColor = color; + outFragColor = params.color; } diff --git a/libraries/gpu/src/gpu/DrawColoredTexture.slf b/libraries/gpu/src/gpu/DrawColoredTexture.slf index 632bf18391..0fe3707b1c 100755 --- a/libraries/gpu/src/gpu/DrawColoredTexture.slf +++ b/libraries/gpu/src/gpu/DrawColoredTexture.slf @@ -13,14 +13,19 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/ShaderConstants.h@> - layout(binding=0) uniform sampler2D colorMap; -layout(location=GPU_UNIFORM_COLOR) uniform vec4 color; + +struct DrawColorParams { + vec4 color; +}; + +layout(binding=0) uniform drawColorParams { + DrawColorParams params; +}; layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; void main(void) { - outFragColor = texture(colorMap, varTexCoord0) * color; + outFragColor = texture(colorMap, varTexCoord0) * params.color; } diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 8fb800291e..12f75049b2 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -184,8 +184,13 @@ void DrawBounds::run(const RenderContextPointer& renderContext, _drawBuffer = std::make_shared(sizeOfItemBound); } - _drawBuffer->setData(numItems * sizeOfItemBound, (const gpu::Byte*) items.data()); + if (!_paramsBuffer) { + _paramsBuffer = std::make_shared(sizeof(vec4), nullptr); + } + _drawBuffer->setData(numItems * sizeOfItemBound, (const gpu::Byte*) items.data()); + glm::vec4 color(glm::vec3(0.0f), -(float) numItems); + _paramsBuffer->setSubData(0, color); gpu::doInBatch("DrawBounds::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; @@ -202,7 +207,7 @@ void DrawBounds::run(const RenderContextPointer& renderContext, batch.setPipeline(getPipeline()); glm::vec4 color(glm::vec3(0.0f), -(float) numItems); - batch._glUniform4fv(gpu::slot::uniform::Color, 1, (const float*)(&color)); + batch.setUniformBuffer(0, _paramsBuffer); batch.setResourceBuffer(0, _drawBuffer); static const int NUM_VERTICES_PER_CUBE = 24; @@ -212,9 +217,10 @@ void DrawBounds::run(const RenderContextPointer& renderContext, gpu::Stream::FormatPointer DrawQuadVolume::_format; -DrawQuadVolume::DrawQuadVolume(const glm::vec3& color) : - _color{ color } { +DrawQuadVolume::DrawQuadVolume(const glm::vec3& color) { _meshVertices = gpu::BufferView(std::make_shared(sizeof(glm::vec3) * 8, nullptr), gpu::Element::VEC3F_XYZ); + _params = std::make_shared(sizeof(glm::vec4), nullptr); + _params->setSubData(0, vec4(color, 1.0)); if (!_format) { _format = std::make_shared(); _format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); @@ -248,8 +254,7 @@ void DrawQuadVolume::run(const render::RenderContextPointer& renderContext, cons batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); batch.setPipeline(getPipeline()); - - batch._glUniform4f(0, _color.x, _color.y, _color.z, 1.0f); + batch.setUniformBuffer(0, _params); batch.setInputFormat(_format); batch.setInputBuffer(gpu::Stream::POSITION, _meshVertices); batch.setIndexBuffer(indices); diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h index 6b98fb2977..1ef4b8caf1 100755 --- a/libraries/render/src/render/DrawTask.h +++ b/libraries/render/src/render/DrawTask.h @@ -66,6 +66,7 @@ private: const gpu::PipelinePointer getPipeline(); gpu::PipelinePointer _boundsPipeline; gpu::BufferPointer _drawBuffer; + gpu::BufferPointer _paramsBuffer; }; class DrawQuadVolumeConfig : public render::JobConfig { @@ -95,7 +96,7 @@ protected: const gpu::BufferView& indices, int indexCount); gpu::BufferView _meshVertices; - glm::vec3 _color; + gpu::BufferPointer _params; bool _isUpdateEnabled{ true }; static gpu::Stream::FormatPointer _format; diff --git a/libraries/render/src/render/drawItemBounds.slv b/libraries/render/src/render/drawItemBounds.slv index 8925009160..ea4d0f24e6 100644 --- a/libraries/render/src/render/drawItemBounds.slv +++ b/libraries/render/src/render/drawItemBounds.slv @@ -20,7 +20,14 @@ <@include gpu/Color.slh@> <$declareColorWheel()$> -layout(location=GPU_UNIFORM_COLOR) uniform vec4 inColor; +struct DrawItemBoundsParams { + vec4 color; +}; + +layout(binding=0) uniform drawItemBoundsParamsBuffer { + DrawItemBoundsParams params; +}; + struct ItemBound { vec4 id_boundPos; @@ -91,10 +98,10 @@ void main(void) { TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, pos, gl_Position)$> - if (inColor.w < 0.0) { - varColor = vec4(colorWheel(float(boundID)/(-inColor.w)), 1.0); + if (params.color.w < 0.0) { + varColor = vec4(colorWheel(float(boundID)/(-params.color.w)), 1.0); } else { - varColor = vec4(colorWheel(float(inColor.w)), 1.0); + varColor = vec4(colorWheel(float(params.color.w)), 1.0); } varTexcoord = vec2(cubeVec.w, length(boundDim)); From 719ae25adb583affd795fd7c9f80a5b26ada64b2 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 23 Aug 2018 16:00:06 -0700 Subject: [PATCH 288/744] Remove uniforms from drawCellBounds --- .../render/src/render/DrawSceneOctree.cpp | 19 +++++++++++++++---- libraries/render/src/render/DrawSceneOctree.h | 2 ++ .../render/src/render/drawCellBounds.slv | 3 +-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 2cb8a5d8ef..60cfc64e27 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -36,6 +36,9 @@ const gpu::PipelinePointer DrawSceneOctree::getDrawCellBoundsPipeline() { // Good to go add the brand new pipeline _drawCellBoundsPipeline = gpu::Pipeline::create(program, state); + _cellBoundsFormat = std::make_shared(); + _cellBoundsFormat->setAttribute(0, 0, gpu::Element(gpu::VEC4, gpu::INT32, gpu::XYZW), 0, gpu::Stream::PER_INSTANCE); + _cellBoundsBuffer = std::make_shared(); } return _drawCellBoundsPipeline; } @@ -82,8 +85,11 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS // bind the one gpu::Pipeline we need batch.setPipeline(getDrawCellBoundsPipeline()); + batch.setInputFormat(_cellBoundsFormat); - auto drawCellBounds = [this, &scene, &batch](const std::vector& cells) { + std::vector cellBounds; + auto drawCellBounds = [this, &cellBounds, &scene, &batch](const std::vector& cells) { + cellBounds.reserve(cellBounds.size() + cells.size()); for (const auto& cellID : cells) { auto cell = scene->getSpatialTree().getConcreteCell(cellID); auto cellLoc = cell.getlocation(); @@ -98,14 +104,19 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS } else if (!empty && !_showVisibleCells) { continue; } - - batch._glUniform4iv(gpu::slot::uniform::Extra0, 1, ((const int*)(&cellLocation))); - batch.draw(gpu::LINES, 24, 0); + cellBounds.push_back(cellLocation); } }; drawCellBounds(inSelection.cellSelection.insideCells); drawCellBounds(inSelection.cellSelection.partialCells); + auto size = cellBounds.size() * sizeof(ivec4); + if (size > _cellBoundsBuffer->getSize()) { + _cellBoundsBuffer->resize(size); + } + _cellBoundsBuffer->setSubData(0, cellBounds); + batch.setInputBuffer(0, _cellBoundsBuffer, 0, sizeof(ivec4)); + batch.drawInstanced(cellBounds.size(), gpu::LINES, 24); // Draw the LOD Reticle { diff --git a/libraries/render/src/render/DrawSceneOctree.h b/libraries/render/src/render/DrawSceneOctree.h index 0b2cd6f685..3f7c07ded3 100644 --- a/libraries/render/src/render/DrawSceneOctree.h +++ b/libraries/render/src/render/DrawSceneOctree.h @@ -54,6 +54,8 @@ namespace render { gpu::PipelinePointer _drawCellBoundsPipeline; gpu::PipelinePointer _drawLODReticlePipeline; gpu::PipelinePointer _drawItemBoundPipeline; + gpu::BufferPointer _cellBoundsBuffer; + gpu::Stream::FormatPointer _cellBoundsFormat; bool _showVisibleCells; // initialized by Config bool _showEmptyCells; // initialized by Config diff --git a/libraries/render/src/render/drawCellBounds.slv b/libraries/render/src/render/drawCellBounds.slv index f3cc4c6411..913a3a372a 100644 --- a/libraries/render/src/render/drawCellBounds.slv +++ b/libraries/render/src/render/drawCellBounds.slv @@ -20,8 +20,7 @@ <$declareColorWheel()$> <@include SceneOctree.slh@> -layout(location=GPU_UNIFORM_EXTRA0) uniform ivec4 inCellLocation; - +layout(location=0) in ivec4 inCellLocation; layout(location=0) out vec4 varColor; void main(void) { From cafd981744e2bc246b647713ab71113e30189935 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 23 Aug 2018 16:17:55 -0700 Subject: [PATCH 289/744] Remove uniforms from bloom debug effect --- .../gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv | 10 ++++++++-- libraries/render-utils/src/BloomEffect.cpp | 7 +++++-- libraries/render-utils/src/BloomEffect.h | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv b/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv index d401fc40c2..8849cb494a 100755 --- a/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv +++ b/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv @@ -21,7 +21,13 @@ <$declareStandardTransform()$> -layout(location=GPU_UNIFORM_TEXCOORD_RECT) uniform vec4 texcoordRect; +struct TexCoordRectParams { + vec4 texcoordRect; +}; + +layout(binding=0) uniform texcoordRectBuffer { + TexCoordRectParams params; +}; layout(location=0) out vec2 varTexCoord0; @@ -39,5 +45,5 @@ void main(void) { TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, pos, gl_Position)$> - varTexCoord0 = ((pos.xy + 1.0) * 0.5) * texcoordRect.zw + texcoordRect.xy; + varTexCoord0 = ((pos.xy + 1.0) * 0.5) * params.texcoordRect.zw + params.texcoordRect.xy; } diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index 3a35e736a9..e6217eb825 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -184,6 +184,7 @@ void BloomDraw::run(const render::RenderContextPointer& renderContext, const Inp } DebugBloom::DebugBloom() { + _params = std::make_shared(sizeof(glm::vec4), nullptr); } void DebugBloom::configure(const Config& config) { @@ -227,7 +228,8 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In Transform modelTransform; if (_mode == DebugBloomConfig::MODE_ALL_LEVELS) { - batch._glUniform4f(gpu::slot::uniform::TexCoordRect, 0.0f, 0.0f, 1.f, 1.f); + _params->setSubData(0, vec4(0.0f, 0.0f, 1.f, 1.f)); + batch.setUniformBuffer(0, _params); modelTransform = gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, args->_viewport / 2); modelTransform.postTranslate(glm::vec3(-1.0f, 1.0f, 0.0f)); @@ -255,7 +257,8 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In viewport.z /= 2; - batch._glUniform4f(gpu::slot::uniform::TexCoordRect, 0.5f, 0.0f, 0.5f, 1.f); + _params->setSubData(0, vec4(0.5f, 0.0f, 0.5f, 1.f)); + batch.setUniformBuffer(0, _params); modelTransform = gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, viewport); modelTransform.postTranslate(glm::vec3(-1.0f, 0.0f, 0.0f)); diff --git a/libraries/render-utils/src/BloomEffect.h b/libraries/render-utils/src/BloomEffect.h index 7789c70190..12c94bb929 100644 --- a/libraries/render-utils/src/BloomEffect.h +++ b/libraries/render-utils/src/BloomEffect.h @@ -121,6 +121,7 @@ public: private: gpu::PipelinePointer _pipeline; + gpu::BufferPointer _params; DebugBloomConfig::Mode _mode; }; From 5a5e1ad4989ed9e544bc5e62fb61bca5a4a82613 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 23 Aug 2018 16:18:10 -0700 Subject: [PATCH 290/744] Remove uniforms from polyvox --- .../src/RenderablePolyVoxEntityItem.cpp | 8 +++++++- .../src/RenderablePolyVoxEntityItem.h | 1 + .../src/entities-renderer/ShaderConstants.h | 12 ------------ libraries/entities-renderer/src/polyvox.slf | 15 +++++++++++---- libraries/entities-renderer/src/polyvox_fade.slf | 14 ++++++++++---- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 2da5c30dc0..292b6368bb 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1609,6 +1609,7 @@ PolyVoxEntityRenderer::PolyVoxEntityRenderer(const EntityItemPointer& entity) : _vertexFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); _vertexFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 12); }); + _params = std::make_shared(sizeof(glm::vec3), nullptr); } ShapeKey PolyVoxEntityRenderer::getShapeKey() { @@ -1671,9 +1672,12 @@ void PolyVoxEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& s void PolyVoxEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { _lastVoxelToWorldMatrix = entity->voxelToWorldMatrix(); + _lastVoxelVolumeSize = entity->getVoxelVolumeSize(); + _params->setSubData(0, _lastVoxelVolumeSize); graphics::MeshPointer newMesh; entity->withReadLock([&] { newMesh = entity->_mesh; + }); if (newMesh && newMesh->getIndexBuffer()._buffer) { @@ -1686,6 +1690,7 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { return; } + PerformanceTimer perfTimer("RenderablePolyVoxEntityItem::render"); gpu::Batch& batch = *args->_batch; @@ -1695,6 +1700,7 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { batch.setInputBuffer(gpu::Stream::POSITION, _mesh->getVertexBuffer()._buffer, 0, sizeof(PolyVox::PositionMaterialNormal)); + // TODO -- should we be setting this? // batch.setInputBuffer(gpu::Stream::NORMAL, mesh->getVertexBuffer()._buffer, // 12, @@ -1710,7 +1716,7 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { } } - batch._glUniform3f(entities_renderer::slot::uniform::PolyvoxVoxelSize, _lastVoxelVolumeSize.x, _lastVoxelVolumeSize.y, _lastVoxelVolumeSize.z); + batch.setUniformBuffer(0, _params); batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)_mesh->getNumIndices(), 0); } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 7afb9b41b4..366a3fdc70 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -187,6 +187,7 @@ private: #endif graphics::MeshPointer _mesh; + gpu::BufferPointer _params; std::array _xyzTextures; glm::vec3 _lastVoxelVolumeSize; glm::mat4 _lastVoxelToWorldMatrix; diff --git a/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h b/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h index 58b0bf5688..d01075846a 100644 --- a/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h +++ b/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h @@ -15,7 +15,6 @@ #define ENTITIES_SHADER_CONSTANTS_H // Polyvox -#define ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE 0 #define ENTITIES_TEXTURE_POLYVOX_XMAP 0 #define ENTITIES_TEXTURE_POLYVOX_YMAP 1 #define ENTITIES_TEXTURE_POLYVOX_ZMAP 2 @@ -26,17 +25,6 @@ namespace entities_renderer { namespace slot { -namespace uniform { -enum Uniform { - PolyvoxVoxelSize = ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE, -}; -} - -namespace buffer { -enum Buffer { -}; -} // namespace buffer - namespace texture { enum Texture { PolyvoxXMap = ENTITIES_TEXTURE_POLYVOX_XMAP, diff --git a/libraries/entities-renderer/src/polyvox.slf b/libraries/entities-renderer/src/polyvox.slf index ba2fd7031b..e45bdc1a6b 100644 --- a/libraries/entities-renderer/src/polyvox.slf +++ b/libraries/entities-renderer/src/polyvox.slf @@ -23,15 +23,22 @@ layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _worldPosition; layout(binding=ENTITIES_TEXTURE_POLYVOX_XMAP) uniform sampler2D xMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_YMAP) uniform sampler2D yMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_ZMAP) uniform sampler2D zMap; -layout(location=ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE) uniform vec3 voxelVolumeSize; + +struct PolyvoxParams { + vec3 voxelVolumeSize; +}; + +layout(binding=0) uniform polyvoxParamsBuffer { + PolyvoxParams params; +}; void main(void) { vec3 worldNormal = cross(dFdy(_worldPosition.xyz), dFdx(_worldPosition.xyz)); worldNormal = normalize(worldNormal); - float inPositionX = (_worldPosition.x - 0.5) / voxelVolumeSize.x; - float inPositionY = (_worldPosition.y - 0.5) / voxelVolumeSize.y; - float inPositionZ = (_worldPosition.z - 0.5) / voxelVolumeSize.z; + float inPositionX = (_worldPosition.x - 0.5) / params.voxelVolumeSize.x; + float inPositionY = (_worldPosition.y - 0.5) / params.voxelVolumeSize.y; + float inPositionZ = (_worldPosition.z - 0.5) / params.voxelVolumeSize.z; vec4 xyDiffuse = texture(xMap, vec2(-inPositionX, -inPositionY)); vec4 xzDiffuse = texture(yMap, vec2(-inPositionX, inPositionZ)); diff --git a/libraries/entities-renderer/src/polyvox_fade.slf b/libraries/entities-renderer/src/polyvox_fade.slf index 2247e472ea..0914debb3c 100644 --- a/libraries/entities-renderer/src/polyvox_fade.slf +++ b/libraries/entities-renderer/src/polyvox_fade.slf @@ -27,7 +27,13 @@ layout(binding=ENTITIES_TEXTURE_POLYVOX_XMAP) uniform sampler2D xMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_YMAP) uniform sampler2D yMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_ZMAP) uniform sampler2D zMap; -layout(location=ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE) uniform vec3 voxelVolumeSize; +struct PolyvoxParams { + vec3 voxelVolumeSize; +}; + +layout(binding=0) uniform polyvoxParamsBuffer { + PolyvoxParams params; +}; // Declare after all samplers to prevent sampler location mix up with voxel shading (sampler locations are hardcoded in RenderablePolyVoxEntityItem) <$declareFadeFragment()$> @@ -42,9 +48,9 @@ void main(void) { vec3 worldNormal = cross(dFdy(_worldPosition.xyz), dFdx(_worldPosition.xyz)); worldNormal = normalize(worldNormal); - float inPositionX = (_worldPosition.x - 0.5) / voxelVolumeSize.x; - float inPositionY = (_worldPosition.y - 0.5) / voxelVolumeSize.y; - float inPositionZ = (_worldPosition.z - 0.5) / voxelVolumeSize.z; + float inPositionX = (_worldPosition.x - 0.5) / params.voxelVolumeSize.x; + float inPositionY = (_worldPosition.y - 0.5) / params.voxelVolumeSize.y; + float inPositionZ = (_worldPosition.z - 0.5) / params.voxelVolumeSize.z; vec4 xyDiffuse = texture(xMap, vec2(-inPositionX, -inPositionY)); vec4 xzDiffuse = texture(yMap, vec2(-inPositionX, inPositionZ)); From 271651737b88d22fbf49bced2956b893a0a0740e Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 23 Aug 2018 16:19:21 -0700 Subject: [PATCH 291/744] Cleanup gpu shader constants --- libraries/gpu/src/gpu/ShaderConstants.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/gpu/src/gpu/ShaderConstants.h b/libraries/gpu/src/gpu/ShaderConstants.h index dc5879e7ad..13dfd1be9c 100644 --- a/libraries/gpu/src/gpu/ShaderConstants.h +++ b/libraries/gpu/src/gpu/ShaderConstants.h @@ -40,8 +40,6 @@ // OSX seems to have an issue using 14 as an attribute location for passing from the vertex to the fragment shader #define GPU_ATTR_V2F_STEREO_SIDE 8 -#define GPU_UNIFORM_COLOR 101 -#define GPU_UNIFORM_TEXCOORD_RECT 102 #define GPU_UNIFORM_EXTRA0 110 #define GPU_UNIFORM_EXTRA1 111 #define GPU_UNIFORM_EXTRA2 112 @@ -98,8 +96,6 @@ enum Attribute { namespace uniform { enum Uniform { - Color = GPU_UNIFORM_COLOR, - TexCoordRect = GPU_UNIFORM_TEXCOORD_RECT, Extra0 = GPU_UNIFORM_EXTRA0, Extra1 = GPU_UNIFORM_EXTRA1, Extra2 = GPU_UNIFORM_EXTRA2, From 07fba89f0f1ef3e03052a2c74167f589fa3c8777 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 24 Aug 2018 10:09:00 -0700 Subject: [PATCH 292/744] Remove uniforms from item status --- libraries/render/src/render/DrawStatus.cpp | 44 ++++++++++++++++--- libraries/render/src/render/DrawStatus.h | 2 + .../render/src/render/drawItemStatus.slv | 8 ++-- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 9b7d4ace2b..4fdee74868 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -170,13 +170,45 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp batch.setPipeline(getDrawItemStatusPipeline()); if (_showNetwork) { - for (size_t i = 0; i < itemBounds.size(); i++) { - batch._glUniform3fv(gpu::slot::uniform::Extra0, 1, (const float*)&itemBounds[i].bound.getCorner()); - batch._glUniform3fv(gpu::slot::uniform::Extra1, 1, ((const float*)&itemBounds[i].bound.getScale())); - batch._glUniform4iv(gpu::slot::uniform::Extra2, 1, (const int*)&(itemStatus[i].first)); - batch._glUniform4iv(gpu::slot::uniform::Extra3, 1, (const int*)&(itemStatus[i].second)); - batch.draw(gpu::TRIANGLES, 24 * NUM_STATUS_VEC4_PER_ITEM, 0); + if (!_instanceBuffer) { + _instanceBuffer = std::make_shared(); } + + struct InstanceData { + vec4 boundPos; + vec4 boundDim; + ivec4 status0; + ivec4 status1; + }; + + if (!_vertexFormat) { + _vertexFormat = std::make_shared(); + _vertexFormat->setAttribute(0, 0, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), offsetof(InstanceData, boundPos), gpu::Stream::PER_INSTANCE); + _vertexFormat->setAttribute(1, 0, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), offsetof(InstanceData, boundDim), gpu::Stream::PER_INSTANCE); + _vertexFormat->setAttribute(2, 0, gpu::Element(gpu::VEC4, gpu::INT32, gpu::XYZW), offsetof(InstanceData, status0), gpu::Stream::PER_INSTANCE); + _vertexFormat->setAttribute(3, 0, gpu::Element(gpu::VEC4, gpu::INT32, gpu::XYZW), offsetof(InstanceData, status1), gpu::Stream::PER_INSTANCE); + } + + batch.setInputFormat(_vertexFormat); + std::vector instanceData; + instanceData.resize(itemBounds.size()); + for (size_t i = 0; i < itemBounds.size(); i++) { + InstanceData& item = instanceData[i]; + const auto& bound = itemBounds[i].bound; + const auto& status = itemStatus[i]; + item.boundPos = vec4(bound.getCorner(), 1.0f); + item.boundDim = vec4(bound.getScale(), 1.0f); + item.status0 = status.first; + item.status1 = status.second; + } + + auto instanceBufferSize = sizeof(InstanceData) * instanceData.size(); + if (_instanceBuffer->getSize() < instanceBufferSize) { + _instanceBuffer->resize(instanceBufferSize); + } + _instanceBuffer->setSubData(0, instanceData); + batch.setInputBuffer(0, _instanceBuffer, 0, sizeof(InstanceData)); + batch.drawInstanced(instanceData.size(), gpu::TRIANGLES, 24 * NUM_STATUS_VEC4_PER_ITEM); } batch.setResourceTexture(0, 0); }); diff --git a/libraries/render/src/render/DrawStatus.h b/libraries/render/src/render/DrawStatus.h index 96269fda4d..2959ca59c5 100644 --- a/libraries/render/src/render/DrawStatus.h +++ b/libraries/render/src/render/DrawStatus.h @@ -63,6 +63,8 @@ namespace render { gpu::PipelinePointer _drawItemStatusPipeline; gpu::BufferPointer _boundsBuffer; + gpu::BufferPointer _instanceBuffer; + gpu::Stream::FormatPointer _vertexFormat; gpu::TexturePointer _statusIconMap; }; } diff --git a/libraries/render/src/render/drawItemStatus.slv b/libraries/render/src/render/drawItemStatus.slv index e92bdda248..7aac26fe2e 100644 --- a/libraries/render/src/render/drawItemStatus.slv +++ b/libraries/render/src/render/drawItemStatus.slv @@ -20,10 +20,10 @@ layout(location=0) out vec4 varColor; layout(location=1) out vec3 varTexcoord; -layout(location=GPU_UNIFORM_EXTRA0) uniform vec3 inBoundPos; -layout(location=GPU_UNIFORM_EXTRA1) uniform vec3 inBoundDim; -layout(location=GPU_UNIFORM_EXTRA2) uniform ivec4 inStatus0; -layout(location=GPU_UNIFORM_EXTRA3) uniform ivec4 inStatus1; +layout(location=0) in vec3 inBoundPos; +layout(location=1) in vec3 inBoundDim; +layout(location=2) in ivec4 inStatus0; +layout(location=3) in ivec4 inStatus1; vec3 paintRainbow(float normalizedHue) { float v = normalizedHue * 6.f; From a4cd56532e1666c5b30cd15f07e8b4c48382300e Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 24 Aug 2018 10:35:51 -0700 Subject: [PATCH 293/744] Removing uniforms from antialiasing --- .../render-utils/src/AntialiasingEffect.cpp | 103 ++++++------------ .../render-utils/src/AntialiasingEffect.h | 15 +-- libraries/render-utils/src/fxaa_blend.slf | 11 +- 3 files changed, 53 insertions(+), 76 deletions(-) diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 2b17ba3c01..b3a645caed 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -29,7 +29,6 @@ namespace ru { - using render_utils::slot::uniform::Uniform; using render_utils::slot::texture::Texture; using render_utils::slot::buffer::Buffer; } @@ -39,13 +38,7 @@ namespace gr { using graphics::slot::buffer::Buffer; } -#define ANTIALIASING_USE_TAA 1 - #if !ANTIALIASING_USE_TAA -#include "fxaa_vert.h" -#include "fxaa_frag.h" -#include "fxaa_blend_frag.h" - Antialiasing::Antialiasing() { _geometryId = DependencyManager::get()->allocateID(); @@ -58,30 +51,9 @@ Antialiasing::~Antialiasing() { } } -const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* args) { - int width = args->_viewport.z; - int height = args->_viewport.w; - - if (_antialiasingBuffer && _antialiasingBuffer->getSize() != uvec2(width, height)) { - _antialiasingBuffer.reset(); - } - - if (!_antialiasingBuffer) { - // Link the antialiasing FBO to texture - _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); - auto format = gpu::Element::COLOR_SRGBA_32; - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - _antialiasingTexture = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); - _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); - } - +const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { if (!_antialiasingPipeline) { - auto vs = fxaa_vert::getShader(); - auto ps = fxaa_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - _texcoordOffsetLoc = program->getUniforms().findLocation("texcoordOffset"); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::fxaa); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(false, false, gpu::LESS_EQUAL); @@ -96,9 +68,7 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* ar const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { if (!_blendPipeline) { - auto vs = fxaa_vert::getShader(); - auto ps = fxaa_blend_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::fxaa_blend); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(false, false, gpu::LESS_EQUAL); PrepareStencil::testNoAA(*state); @@ -119,13 +89,30 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.enableStereo(false); batch.setViewportTransform(args->_viewport); - // FIXME: NEED to simplify that code to avoid all the GeometryCahce call, this is purely pixel manipulation - float fbWidth = renderContext->args->_viewport.z; - float fbHeight = renderContext->args->_viewport.w; - // float sMin = args->_viewport.x / fbWidth; - // float sWidth = args->_viewport.z / fbWidth; - // float tMin = args->_viewport.y / fbHeight; - // float tHeight = args->_viewport.w / fbHeight; + if (!_paramsBuffer) { + _paramsBuffer = std::make_shared(sizeof(glm::vec2), nullptr); + } + + { + int width = args->_viewport.z; + int height = args->_viewport.w; + if (_antialiasingBuffer && _antialiasingBuffer->getSize() != uvec2(width, height)) { + _antialiasingBuffer.reset(); + } + + if (!_antialiasingBuffer) { + // Link the antialiasing FBO to texture + _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); + auto format = gpu::Element::COLOR_SRGBA_32; + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); + _antialiasingTexture = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); + _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); + glm::vec2 fbExtent { args->_viewport.z, args->_viewport.w }; + glm::vec2 inverseFbExtent = 1.0f / fbExtent; + _paramsBuffer->setSubData(0, inverseFbExtent); + } + } + glm::mat4 projMat; Transform viewMat; @@ -136,40 +123,18 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setModelTransform(Transform()); // FXAA step - auto pipeline = getAntialiasingPipeline(renderContext->args); + auto pipeline = getAntialiasingPipeline(); batch.setResourceTexture(0, sourceBuffer->getRenderBuffer(0)); batch.setFramebuffer(_antialiasingBuffer); batch.setPipeline(pipeline); - - // initialize the view-space unpacking uniforms using frustum data - float left, right, bottom, top, nearVal, farVal; - glm::vec4 nearClipPlane, farClipPlane; - - args->getViewFrustum().computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - - // float depthScale = (farVal - nearVal) / farVal; - // float nearScale = -1.0f / nearVal; - // float depthTexCoordScaleS = (right - left) * nearScale / sWidth; - // float depthTexCoordScaleT = (top - bottom) * nearScale / tHeight; - // float depthTexCoordOffsetS = left * nearScale - sMin * depthTexCoordScaleS; - // float depthTexCoordOffsetT = bottom * nearScale - tMin * depthTexCoordScaleT; - - batch._glUniform2f(_texcoordOffsetLoc, 1.0f / fbWidth, 1.0f / fbHeight); - - glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f); - glm::vec2 bottomLeft(-1.0f, -1.0f); - glm::vec2 topRight(1.0f, 1.0f); - glm::vec2 texCoordTopLeft(0.0f, 0.0f); - glm::vec2 texCoordBottomRight(1.0f, 1.0f); - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color, _geometryId); - + batch.setUniformBuffer(0, _paramsBuffer); + batch.draw(gpu::TRIANGLE_STRIP, 4); // Blend step batch.setResourceTexture(0, _antialiasingTexture); batch.setFramebuffer(sourceBuffer); batch.setPipeline(getBlendPipeline()); - - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color, _geometryId); + batch.draw(gpu::TRIANGLE_STRIP, 4); }); } #else @@ -314,7 +279,11 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const // Must match the bindg point in the fxaa_blend.slf shader batch.setResourceFramebufferSwapChainTexture(0, _antialiasingBuffers, 1); // Disable sharpen if FXAA - batch._glUniform1f(ru::Uniform::TaaSharpenIntensity, _sharpen * _params.get().regionInfo.z); + if (!_blendParamsBuffer) { + _blendParamsBuffer = std::make_shared(sizeof(float), nullptr); + } + _blendParamsBuffer->setSubData(0, _sharpen * _params.get().regionInfo.z); + batch.setUniformBuffer(0, _blendParamsBuffer); } batch.draw(gpu::TRIANGLE_STRIP, 4); batch.advance(_antialiasingBuffers); diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index 61da352154..936ade043d 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -134,6 +134,10 @@ signals: #define SET_BIT(bitfield, bitIndex, value) bitfield = ((bitfield) & ~(1 << (bitIndex))) | ((value) << (bitIndex)) #define GET_BIT(bitfield, bitIndex) ((bitfield) & (1 << (bitIndex))) +#define ANTIALIASING_USE_TAA 1 + +#if ANTIALIASING_USE_TAA + struct TAAParams { float nope{ 0.0f }; float blend{ 0.15f }; @@ -186,7 +190,7 @@ private: gpu::FramebufferSwapChainPointer _antialiasingBuffers; gpu::TexturePointer _antialiasingTextures[2]; - + gpu::BufferPointer _blendParamsBuffer; gpu::PipelinePointer _antialiasingPipeline; gpu::PipelinePointer _blendPipeline; gpu::PipelinePointer _debugBlendPipeline; @@ -197,7 +201,7 @@ private: }; -/* +#else class AntiAliasingConfig : public render::Job::Config { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled) @@ -219,18 +223,15 @@ public: const gpu::PipelinePointer& getBlendPipeline(); private: - - // Uniforms for AA - gpu::int32 _texcoordOffsetLoc; - gpu::FramebufferPointer _antialiasingBuffer; gpu::TexturePointer _antialiasingTexture; + gpu::BufferPointer _paramsBuffer; gpu::PipelinePointer _antialiasingPipeline; gpu::PipelinePointer _blendPipeline; int _geometryId { 0 }; }; -*/ +#endif #endif // hifi_AntialiasingEffect_h diff --git a/libraries/render-utils/src/fxaa_blend.slf b/libraries/render-utils/src/fxaa_blend.slf index aca050f047..b2515ccd7f 100644 --- a/libraries/render-utils/src/fxaa_blend.slf +++ b/libraries/render-utils/src/fxaa_blend.slf @@ -18,7 +18,14 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; layout(binding=0) uniform sampler2D colorTexture; -layout(location=GPU_UNIFORM_EXTRA0) uniform float sharpenIntensity; + +struct FxaaBlendParams { + float sharpenIntensity; +}; + +layout(binding=0) uniform fxaaBlendParamsBuffer { + FxaaBlendParams params; +}; void main(void) { vec4 pixels[9]; @@ -39,5 +46,5 @@ void main(void) { vec4 minColor = max(vec4(0), pixels[4]-vec4(0.5)); vec4 maxColor = pixels[4]+vec4(0.5); - outFragColor = clamp(pixels[4] + sharpenedPixel * sharpenIntensity, minColor, maxColor); + outFragColor = clamp(pixels[4] + sharpenedPixel * params.sharpenIntensity, minColor, maxColor); } From f704ddc11c7a2a0542a46cb5047d140bf554a2f8 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 24 Aug 2018 23:25:36 -0700 Subject: [PATCH 294/744] Remove uniform from SDF text rendering --- libraries/render-utils/src/TextRenderer3D.cpp | 3 +- libraries/render-utils/src/TextRenderer3D.h | 4 +- libraries/render-utils/src/sdf_text3D.slf | 17 +++-- .../src/sdf_text3D_transparent.slf | 17 +++-- libraries/render-utils/src/text/Font.cpp | 70 +++++++++++-------- libraries/render-utils/src/text/Font.h | 34 +++++---- 6 files changed, 92 insertions(+), 53 deletions(-) diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index dd9167d248..8ef0dc0d73 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -70,9 +70,8 @@ void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& st const glm::vec2& bounds, bool layered) { // The font does all the OpenGL work if (_font) { - // Cache color so that the pointer stays valid. _color = color; - _font->drawString(batch, x, y, str, &_color, _effectType, bounds, layered); + _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds, layered); } } diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index 175802ef6e..6c91411e1d 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -15,12 +15,14 @@ #include #include #include +#include namespace gpu { class Batch; } class Font; +#include "text/Font.h" #include "text/EffectType.h" #include "text/FontFamilies.h" @@ -51,7 +53,7 @@ private: // text color glm::vec4 _color; - + Font::DrawInfo _drawInfo; std::shared_ptr _font; }; diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index 2fbaa03900..c002d0c262 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -14,8 +14,15 @@ <@include render-utils/ShaderConstants.h@> layout(binding=0) uniform sampler2D Font; -layout(location=RENDER_UTILS_UNIFORM_TEXT_OUTLINE) uniform bool Outline; -layout(location=RENDER_UTILS_UNIFORM_TEXT_COLOR) uniform vec4 Color; + +struct TextParams { + vec4 color; + float outline; +}; + +layout(binding=0) uniform textParamsBuffer { + TextParams params; +}; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; @@ -32,7 +39,7 @@ const float taaBias = pow(2.0, TAA_TEXTURE_LOD_BIAS); float evalSDF(vec2 texCoord) { // retrieve signed distance float sdf = textureLod(Font, texCoord, TAA_TEXTURE_LOD_BIAS).g; - if (Outline) { + if (params.outline > 0.0) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; } else { @@ -62,8 +69,8 @@ void main() { packDeferredFragment( normalize(_normalWS), - a * Color.a, - Color.rgb, + a * params.color.a, + params.color.rgb, DEFAULT_ROUGHNESS, DEFAULT_METALLIC, DEFAULT_EMISSIVE, diff --git a/libraries/render-utils/src/sdf_text3D_transparent.slf b/libraries/render-utils/src/sdf_text3D_transparent.slf index 218236c26b..d4c4fcc409 100644 --- a/libraries/render-utils/src/sdf_text3D_transparent.slf +++ b/libraries/render-utils/src/sdf_text3D_transparent.slf @@ -14,8 +14,15 @@ <@include render-utils/ShaderConstants.h@> layout(binding=0) uniform sampler2D Font; -layout(location=RENDER_UTILS_UNIFORM_TEXT_OUTLINE) uniform bool Outline; -layout(location=RENDER_UTILS_UNIFORM_TEXT_COLOR) uniform vec4 Color; + +struct TextParams { + vec4 color; + float outline; +}; + +layout(binding=0) uniform textParamsBuffer { + TextParams params; +}; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; @@ -31,7 +38,7 @@ const float outlineExpansion = 0.2; void main() { // retrieve signed distance float sdf = texture(Font, _texCoord0).g; - if (Outline) { + if (params.outline > 0.0) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; } else { @@ -51,8 +58,8 @@ void main() { packDeferredFragmentTranslucent( normalize(_normalWS), - a * Color.a, - Color.rgb, + a * params.color.a, + params.color.rgb, DEFAULT_FRESNEL, DEFAULT_ROUGHNESS); } \ No newline at end of file diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index aca6ddb79f..5ae50a8c73 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -256,29 +256,30 @@ void Font::setupGPU() { } } -void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2& bounds) { - _verticesBuffer = std::make_shared(); - _numVertices = 0; - _indicesBuffer = std::make_shared(); - _numIndices = 0; +void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds) { + drawInfo.verticesBuffer = std::make_shared(); + drawInfo.indicesBuffer = std::make_shared(); + drawInfo.indexCount = 0; + int numVertices = 0; - _lastStringRendered = str; - _lastBounds = bounds; + drawInfo.string = str; + drawInfo.bounds = bounds; + drawInfo.origin = origin; // Top left of text - glm::vec2 advance = glm::vec2(x, y); + glm::vec2 advance = origin; foreach(const QString& token, tokenizeForWrapping(str)) { bool isNewLine = (token == QString('\n')); bool forceNewLine = false; // Handle wrapping - if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > x + bounds.x)) { + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > origin.x + bounds.x)) { // We are out of the x bound, force new line forceNewLine = true; } if (isNewLine || forceNewLine) { // Character return, move the advance to a new line - advance = glm::vec2(x, advance.y - _leading); + advance = glm::vec2(origin.x, advance.y - _leading); if (isNewLine) { // No need to draw anything, go directly to next token @@ -288,7 +289,7 @@ void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2 break; } } - if ((bounds.y != -1) && (advance.y - _fontSize < -y - bounds.y)) { + if ((bounds.y != -1) && (advance.y - _fontSize < -origin.y - bounds.y)) { // We are out of the y bound, stop drawing break; } @@ -297,11 +298,11 @@ void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2 if (!isNewLine) { for (auto c : token) { auto glyph = _glyphs[c]; - quint16 verticesOffset = _numVertices; + quint16 verticesOffset = numVertices; QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _ascent)); - _verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd); - _numVertices += 4; + drawInfo.verticesBuffer->append(qd); + numVertices += 4; // Sam's recommended triangle slices // Triangle tri1 = { v0, v1, v3 }; @@ -327,8 +328,8 @@ void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2 indices[3] = verticesOffset + 2; indices[4] = verticesOffset + 1; indices[5] = verticesOffset + 3; - _indicesBuffer->append(sizeof(indices), (const gpu::Byte*)indices); - _numIndices += NUMBER_OF_INDICES_PER_QUAD; + drawInfo.indicesBuffer->append(sizeof(indices), (const gpu::Byte*)indices); + drawInfo.indexCount += NUMBER_OF_INDICES_PER_QUAD; // Advance by glyph size @@ -341,26 +342,39 @@ void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2 } } -void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4* color, - EffectType effectType, const glm::vec2& bounds, bool layered) { +void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString& str, const glm::vec4& color, + EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds, bool layered) { if (str == "") { return; } - if (str != _lastStringRendered || bounds != _lastBounds) { - rebuildVertices(x, y, str, bounds); + if (str != drawInfo.string || bounds != drawInfo.bounds || origin != drawInfo.origin) { + buildVertices(drawInfo, str, origin, bounds); } setupGPU(); - batch.setPipeline(((*color).a < 1.0f || layered) ? _transparentPipeline : _pipeline); - batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); - batch._glUniform1i(render_utils::slot::uniform::TextOutline, (effectType == OUTLINE_EFFECT)); + struct GpuDrawParams { + glm::vec4 color; + float outline; + }; + + if (!drawInfo.paramsBuffer || drawInfo.params.color != color || drawInfo.params.effect != effectType) { + drawInfo.params.color = color; + drawInfo.params.effect = effectType; + GpuDrawParams gpuDrawParams; + gpuDrawParams.color = ColorUtils::sRGBToLinearVec4(drawInfo.params.color); + gpuDrawParams.outline = (drawInfo.params.effect == OUTLINE_EFFECT) ? 1 : 0; + drawInfo.paramsBuffer = std::make_shared(sizeof(GpuDrawParams), nullptr); + drawInfo.paramsBuffer->setSubData(0, sizeof(GpuDrawParams), (const gpu::Byte*)&gpuDrawParams); + } // need the gamma corrected color here - glm::vec4 lrgba = ColorUtils::sRGBToLinearVec4(*color); - batch._glUniform4fv(render_utils::slot::uniform::TextColor, 1, (const float*)&lrgba); + + batch.setPipeline((color.a < 1.0f || layered) ? _transparentPipeline : _pipeline); batch.setInputFormat(_format); - batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); - batch.setIndexBuffer(gpu::UINT16, _indicesBuffer, 0); - batch.drawIndexed(gpu::TRIANGLES, _numIndices, 0); + batch.setInputBuffer(0, drawInfo.verticesBuffer, 0, _format->getChannels().at(0)._stride); + batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); + batch.setUniformBuffer(0, drawInfo.paramsBuffer, 0, sizeof(GpuDrawParams)); + batch.setIndexBuffer(gpu::UINT16, drawInfo.indicesBuffer, 0); + batch.drawIndexed(gpu::TRIANGLES, drawInfo.indexCount, 0); } diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index 2fa2b65fa5..9a0a9b4734 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -23,16 +23,34 @@ public: void read(QIODevice& path); + struct DrawParams { + vec4 color{ -1 }; + EffectType effect; + }; + + struct DrawInfo { + gpu::BufferPointer verticesBuffer; + gpu::BufferPointer indicesBuffer; + gpu::BufferPointer paramsBuffer; + uint32_t indexCount; + + QString string; + glm::vec2 origin; + glm::vec2 bounds; + DrawParams params; + }; + glm::vec2 computeExtent(const QString& str) const; float getFontSize() const { return _fontSize; } // Render string to batch - void drawString(gpu::Batch& batch, float x, float y, const QString& str, - const glm::vec4* color, EffectType effectType, - const glm::vec2& bound, bool layered = false); + void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str, + const glm::vec4& color, EffectType effectType, + const glm::vec2& origin, const glm::vec2& bound, bool layered = false); static Pointer load(const QString& family); + private: static Pointer load(QIODevice& fontFile); QStringList tokenizeForWrapping(const QString& str) const; @@ -40,7 +58,7 @@ private: glm::vec2 computeTokenExtent(const QString& str) const; const Glyph& getGlyph(const QChar& c) const; - void rebuildVertices(float x, float y, const QString& str, const glm::vec2& bounds); + void buildVertices(DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds); void setupGPU(); @@ -66,15 +84,7 @@ private: gpu::PipelinePointer _transparentPipeline; gpu::TexturePointer _texture; gpu::Stream::FormatPointer _format; - gpu::BufferPointer _verticesBuffer; - gpu::BufferPointer _indicesBuffer; gpu::BufferStreamPointer _stream; - unsigned int _numVertices = 0; - unsigned int _numIndices = 0; - - // last string render characteristics - QString _lastStringRendered; - glm::vec2 _lastBounds; }; #endif From 730cdb4257f6543af89e1671cc5a1c97b4411e2f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 24 Aug 2018 23:43:44 -0700 Subject: [PATCH 295/744] Remove uniforms from unused shaders --- libraries/render-utils/src/deferred_light_limited.slv | 3 ++- libraries/render-utils/src/fxaa.slf | 4 +++- .../render-utils/src/subsurfaceScattering_drawScattering.slf | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/libraries/render-utils/src/deferred_light_limited.slv b/libraries/render-utils/src/deferred_light_limited.slv index fb59b8e78f..7aaec98e72 100644 --- a/libraries/render-utils/src/deferred_light_limited.slv +++ b/libraries/render-utils/src/deferred_light_limited.slv @@ -18,7 +18,8 @@ <$declareStandardTransform()$> -uniform vec4 sphereParam; +// FIXME make into a uniform buffer or push constant if this shader ever comes into use +vec4 sphereParam = vec4(0.0); layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; diff --git a/libraries/render-utils/src/fxaa.slf b/libraries/render-utils/src/fxaa.slf index 2dddcc795b..2c5e1cdc31 100644 --- a/libraries/render-utils/src/fxaa.slf +++ b/libraries/render-utils/src/fxaa.slf @@ -24,7 +24,9 @@ precision mediump int; layout(binding=0) uniform sampler2D colorTexture; //uniform sampler2D historyTexture; -layout(location=0) uniform vec2 texcoordOffset; + +// FIXME make into a uniform buffer or push constant if this shader ever comes into use +vec2 texcoordOffset = vec2(0.0); layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; diff --git a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf index 00b60d6fd4..9410a2ed2b 100644 --- a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf +++ b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf @@ -26,7 +26,8 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 _fragColor; -layout(location=GPU_UNIFORM_EXTRA0) uniform vec2 uniformCursorTexcoord = vec2(0.5); +// FIXME make into a uniform buffer or push constant if this shader ever comes into use +vec2 uniformCursorTexcoord = vec2(0.5); //uniform vec3 uniformLightVector = vec3(1.0); From 87b9594bd0d97647c49fb77058b88996a44d3385 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sat, 25 Aug 2018 09:54:06 -0700 Subject: [PATCH 296/744] Fix warnings --- libraries/render/src/render/DrawSceneOctree.cpp | 2 +- libraries/render/src/render/DrawStatus.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 60cfc64e27..6c87d2f56b 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -116,7 +116,7 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS } _cellBoundsBuffer->setSubData(0, cellBounds); batch.setInputBuffer(0, _cellBoundsBuffer, 0, sizeof(ivec4)); - batch.drawInstanced(cellBounds.size(), gpu::LINES, 24); + batch.drawInstanced((uint32_t)cellBounds.size(), gpu::LINES, 24); // Draw the LOD Reticle { diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 4fdee74868..a1b61a4e77 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -208,7 +208,7 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp } _instanceBuffer->setSubData(0, instanceData); batch.setInputBuffer(0, _instanceBuffer, 0, sizeof(InstanceData)); - batch.drawInstanced(instanceData.size(), gpu::TRIANGLES, 24 * NUM_STATUS_VEC4_PER_ITEM); + batch.drawInstanced((uint32_t)instanceData.size(), gpu::TRIANGLES, 24 * NUM_STATUS_VEC4_PER_ITEM); } batch.setResourceTexture(0, 0); }); From 2df2bbef4662b0e5cd408504e7d793777b24deb6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 28 Aug 2018 09:58:03 -0700 Subject: [PATCH 297/744] Adding missing locations --- libraries/render-utils/src/Highlight.slh | 4 ++-- libraries/render-utils/src/deferred_light_limited.slv | 4 ++-- libraries/render-utils/src/drawWorkloadProxy.slf | 6 +++--- libraries/render-utils/src/drawWorkloadProxy.slv | 6 +++--- libraries/render-utils/src/drawWorkloadView.slf | 6 +++--- libraries/render-utils/src/drawWorkloadView.slv | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/libraries/render-utils/src/Highlight.slh b/libraries/render-utils/src/Highlight.slh index 56a999f508..9e988a6120 100644 --- a/libraries/render-utils/src/Highlight.slh +++ b/libraries/render-utils/src/Highlight.slh @@ -22,8 +22,8 @@ layout(binding=RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS) uniform highlightParamsBuff layout(binding=RENDER_UTILS_TEXTURE_HIGHLIGHT_SCENE_DEPTH) uniform sampler2D sceneDepthMap; layout(binding=RENDER_UTILS_TEXTURE_HIGHLIGHT_DEPTH) uniform sampler2D highlightedDepthMap; -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; const float FAR_Z = 1.0; const float LINEAR_DEPTH_BIAS = 5e-3; diff --git a/libraries/render-utils/src/deferred_light_limited.slv b/libraries/render-utils/src/deferred_light_limited.slv index 7aaec98e72..1c835aacf7 100644 --- a/libraries/render-utils/src/deferred_light_limited.slv +++ b/libraries/render-utils/src/deferred_light_limited.slv @@ -42,7 +42,7 @@ void main(void) { } #endif #endif - _texCoord01.xy = vec4(projected.xy, 0.0, 1.0) * gl_Position.w; + _texCoord01 = vec4(projected.xy, 0.0, 1.0) * gl_Position.w; } else { const float depth = -1.0; //Draw at near plane const vec4 UNIT_QUAD[4] = vec4[4]( @@ -61,7 +61,7 @@ void main(void) { #endif #endif - _texCoord01.xy = vec4((pos.xy + 1.0) * 0.5, 0.0, 1.0); + _texCoord01 = vec4((pos.xy + 1.0) * 0.5, 0.0, 1.0); #ifdef GPU_TRANSFORM_IS_STEREO #ifdef GPU_TRANSFORM_STEREO_SPLIT_SCREEN diff --git a/libraries/render-utils/src/drawWorkloadProxy.slf b/libraries/render-utils/src/drawWorkloadProxy.slf index 32dceab00a..f0bd9d474c 100644 --- a/libraries/render-utils/src/drawWorkloadProxy.slf +++ b/libraries/render-utils/src/drawWorkloadProxy.slf @@ -13,9 +13,9 @@ <@include DeferredBufferWrite.slh@> <@include gpu/Paint.slh@> -in vec4 varColor; -in vec3 varTexcoord; -in vec3 varEyePos; +layout(location=0) in vec4 varColor; +layout(location=1) in vec3 varTexcoord; +layout(location=2) in vec3 varEyePos; void main(void) { if (varColor.w > 0.0) { diff --git a/libraries/render-utils/src/drawWorkloadProxy.slv b/libraries/render-utils/src/drawWorkloadProxy.slv index 28a62070f9..7a01702107 100644 --- a/libraries/render-utils/src/drawWorkloadProxy.slv +++ b/libraries/render-utils/src/drawWorkloadProxy.slv @@ -19,9 +19,9 @@ <$declareWorkloadProxies()$> -out vec4 varColor; -out vec3 varTexcoord; -out vec3 varEyePos; +layout(location=0) out vec4 varColor; +layout(location=1) out vec3 varTexcoord; +layout(location=2) out vec3 varEyePos; void main(void) { const vec4 UNIT_SPRITE[3] = vec4[3]( diff --git a/libraries/render-utils/src/drawWorkloadView.slf b/libraries/render-utils/src/drawWorkloadView.slf index c44dae4a24..b638824204 100644 --- a/libraries/render-utils/src/drawWorkloadView.slf +++ b/libraries/render-utils/src/drawWorkloadView.slf @@ -14,9 +14,9 @@ <@include DeferredBufferWrite.slh@> <@include gpu/Paint.slh@> -in vec4 varColor; -in vec3 varTexcoord; -in vec3 varEyePos; +layout(location=0) in vec4 varColor; +layout(location=1) in vec3 varTexcoord; +layout(location=2) in vec3 varEyePos; void main(void) { if (varColor.w > 0.0) { diff --git a/libraries/render-utils/src/drawWorkloadView.slv b/libraries/render-utils/src/drawWorkloadView.slv index 291a8c86cd..0e32c36e66 100644 --- a/libraries/render-utils/src/drawWorkloadView.slv +++ b/libraries/render-utils/src/drawWorkloadView.slv @@ -18,9 +18,9 @@ <@include WorkloadResource.slh@> <$declareWorkloadViews()$> -out vec4 varColor; -out vec3 varTexcoord; -out vec3 varEyePos; +layout(location=0) out vec4 varColor; +layout(location=1) out vec3 varTexcoord; +layout(location=2) out vec3 varEyePos; const int NUM_VERTICES_PER_SEGMENT = 2; const int NUM_SEGMENT_PER_VIEW_REGION = 65; From 9a28e04e37c3723da8f26f08dd6e9597246ce71a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 28 Aug 2018 11:17:24 -0700 Subject: [PATCH 298/744] Remove all tabs from shaders --- libraries/gpu/src/gpu/Color.slh | 34 +-- libraries/gpu/src/gpu/Noise.slh | 14 +- .../gpu/src/gpu/TransformCamera_shared.slh | 4 +- .../src/BloomThreshold.shared.slh | 2 +- .../render-utils/src/DeferredBufferRead.slh | 2 +- .../render-utils/src/DeferredTransform.slh | 10 +- libraries/render-utils/src/Fade_shared.slh | 12 +- libraries/render-utils/src/Shadow.slh | 2 +- libraries/render-utils/src/ShadowCore.slh | 14 +- libraries/render-utils/src/Shadows_shared.slh | 4 +- .../src/debug_deferred_buffer_shared.slh | 2 +- libraries/render-utils/src/fxaa_blend.slf | 4 +- libraries/render-utils/src/glowLine.slf | 6 +- .../src/model_translucent_normal_map.slf | 2 +- .../src/model_translucent_normal_map_fade.slf | 2 +- libraries/render-utils/src/sdf_text3D.slf | 10 +- .../src/surfaceGeometry_copyDepth.slf | 4 +- libraries/render-utils/src/taa.slh | 250 +++++++++--------- libraries/render-utils/src/taa_blend.slf | 10 +- .../src/velocityBuffer_cameraMotion.slf | 6 +- .../render-utils/src/zone_drawKeyLight.slf | 4 +- .../render/src/render/drawItemStatus.slf | 2 +- 22 files changed, 200 insertions(+), 200 deletions(-) diff --git a/libraries/gpu/src/gpu/Color.slh b/libraries/gpu/src/gpu/Color.slh index 384b8bd329..16eb7487ed 100644 --- a/libraries/gpu/src/gpu/Color.slh +++ b/libraries/gpu/src/gpu/Color.slh @@ -31,29 +31,29 @@ vec4 color_sRGBAToLinear(vec4 srgba) { } vec3 color_LinearToYCoCg(vec3 rgb) { - // Y = R/4 + G/2 + B/4 - // Co = R/2 - B/2 - // Cg = -R/4 + G/2 - B/4 - return vec3( - rgb.x/4.0 + rgb.y/2.0 + rgb.z/4.0, - rgb.x/2.0 - rgb.z/2.0, - -rgb.x/4.0 + rgb.y/2.0 - rgb.z/4.0 - ); + // Y = R/4 + G/2 + B/4 + // Co = R/2 - B/2 + // Cg = -R/4 + G/2 - B/4 + return vec3( + rgb.x/4.0 + rgb.y/2.0 + rgb.z/4.0, + rgb.x/2.0 - rgb.z/2.0, + -rgb.x/4.0 + rgb.y/2.0 - rgb.z/4.0 + ); } vec3 color_YCoCgToUnclampedLinear(vec3 ycocg) { - // R = Y + Co - Cg - // G = Y + Cg - // B = Y - Co - Cg - return vec3( - ycocg.x + ycocg.y - ycocg.z, - ycocg.x + ycocg.z, - ycocg.x - ycocg.y - ycocg.z - ); + // R = Y + Co - Cg + // G = Y + Cg + // B = Y - Co - Cg + return vec3( + ycocg.x + ycocg.y - ycocg.z, + ycocg.x + ycocg.z, + ycocg.x - ycocg.y - ycocg.z + ); } vec3 color_YCoCgToLinear(vec3 ycocg) { - return clamp(color_YCoCgToUnclampedLinear(ycocg), vec3(0.0), vec3(1.0)); + return clamp(color_YCoCgToUnclampedLinear(ycocg), vec3(0.0), vec3(1.0)); } <@func declareColorWheel()@> diff --git a/libraries/gpu/src/gpu/Noise.slh b/libraries/gpu/src/gpu/Noise.slh index b390945957..d300e71ba9 100644 --- a/libraries/gpu/src/gpu/Noise.slh +++ b/libraries/gpu/src/gpu/Noise.slh @@ -284,14 +284,14 @@ float hifi_noise(in vec2 x) { // https://www.shadertoy.com/view/MdX3Rr // https://en.wikipedia.org/wiki/Fractional_Brownian_motion float hifi_fbm(in vec2 p) { - const mat2 m2 = mat2(0.8, -0.6, 0.6, 0.8); - float f = 0.0; - f += 0.5000 * hifi_noise(p); p = m2 * p * 2.02; - f += 0.2500 * hifi_noise(p); p = m2 * p * 2.03; - f += 0.1250 * hifi_noise(p); p = m2 * p * 2.01; - f += 0.0625 * hifi_noise(p); + const mat2 m2 = mat2(0.8, -0.6, 0.6, 0.8); + float f = 0.0; + f += 0.5000 * hifi_noise(p); p = m2 * p * 2.02; + f += 0.2500 * hifi_noise(p); p = m2 * p * 2.03; + f += 0.1250 * hifi_noise(p); p = m2 * p * 2.01; + f += 0.0625 * hifi_noise(p); - return f / 0.9375; + return f / 0.9375; } <@endif@> \ No newline at end of file diff --git a/libraries/gpu/src/gpu/TransformCamera_shared.slh b/libraries/gpu/src/gpu/TransformCamera_shared.slh index 37521d8201..e4a0f8c2cc 100644 --- a/libraries/gpu/src/gpu/TransformCamera_shared.slh +++ b/libraries/gpu/src/gpu/TransformCamera_shared.slh @@ -2,11 +2,11 @@ #ifdef __cplusplus # define _MAT4 Mat4 # define _VEC4 Vec4 -# define _MUTABLE mutable +# define _MUTABLE mutable #else # define _MAT4 mat4 # define _VEC4 vec4 -# define _MUTABLE +# define _MUTABLE #endif struct _TransformCamera { diff --git a/libraries/render-utils/src/BloomThreshold.shared.slh b/libraries/render-utils/src/BloomThreshold.shared.slh index 8aaf8ec311..5ad490a1ca 100644 --- a/libraries/render-utils/src/BloomThreshold.shared.slh +++ b/libraries/render-utils/src/BloomThreshold.shared.slh @@ -8,7 +8,7 @@ struct Parameters { BT_VEC2 _deltaUV; - float _threshold; + float _threshold; int _sampleCount; }; diff --git a/libraries/render-utils/src/DeferredBufferRead.slh b/libraries/render-utils/src/DeferredBufferRead.slh index 8c6c76b24a..e5a7c39d54 100644 --- a/libraries/render-utils/src/DeferredBufferRead.slh +++ b/libraries/render-utils/src/DeferredBufferRead.slh @@ -137,7 +137,7 @@ vec4 unpackDeferredPosition(float depthValue, vec2 texcoord) { // This method to unpack position is fastesst vec4 unpackDeferredPositionFromZdb(vec2 texcoord) { float Zdb = texture(depthMap, texcoord).x; - return unpackDeferredPosition(Zdb, texcoord); + return unpackDeferredPosition(Zdb, texcoord); } vec4 unpackDeferredPositionFromZeye(vec2 texcoord) { diff --git a/libraries/render-utils/src/DeferredTransform.slh b/libraries/render-utils/src/DeferredTransform.slh index 6dd92c651b..19986805f6 100644 --- a/libraries/render-utils/src/DeferredTransform.slh +++ b/libraries/render-utils/src/DeferredTransform.slh @@ -38,8 +38,8 @@ struct DeferredFrameTransform { mat4 _projectionMono; mat4 _viewInverse; mat4 _view; - mat4 _projectionUnJittered[2]; - mat4 _invProjectionUnJittered[2]; + mat4 _projectionUnJittered[2]; + mat4 _invProjectionUnJittered[2]; }; layout(binding=RENDER_UTILS_BUFFER_DEFERRED_FRAME_TRANSFORM) uniform deferredFrameTransformBuffer { @@ -68,10 +68,10 @@ mat4 getProjectionMono() { return frameTransform._projectionMono; } mat4 getUnjitteredProjection(int side) { - return frameTransform._projectionUnJittered[side]; + return frameTransform._projectionUnJittered[side]; } mat4 getUnjitteredInvProjection(int side) { - return frameTransform._invProjectionUnJittered[side]; + return frameTransform._invProjectionUnJittered[side]; } // positive near distance of the projection @@ -158,7 +158,7 @@ vec3 evalUnjitteredEyePositionFromZdb(int side, float Zdb, vec2 texcoord) { } vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) { - float Zdb = evalZdbFromZeye(Zeye); + float Zdb = evalZdbFromZeye(Zeye); return evalEyePositionFromZdb(side, Zdb, texcoord); } diff --git a/libraries/render-utils/src/Fade_shared.slh b/libraries/render-utils/src/Fade_shared.slh index 7d1c0fb8bc..7ede92b807 100644 --- a/libraries/render-utils/src/Fade_shared.slh +++ b/libraries/render-utils/src/Fade_shared.slh @@ -13,12 +13,12 @@ struct FadeParameters { - VEC4 _noiseInvSizeAndLevel; - VEC4 _innerEdgeColor; - VEC4 _outerEdgeColor; - VEC2 _edgeWidthInvWidth; - FLOAT32 _baseLevel; - INT32 _isInverted; + VEC4 _noiseInvSizeAndLevel; + VEC4 _innerEdgeColor; + VEC4 _outerEdgeColor; + VEC2 _edgeWidthInvWidth; + FLOAT32 _baseLevel; + INT32 _isInverted; }; // <@if 1@> diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 85a30d5889..db1c3b4d85 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -45,7 +45,7 @@ struct ShadowSampleOffsets { }; ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) { - float shadowScale = getShadowScale(); + float shadowScale = getShadowScale(); ShadowSampleOffsets offsets; #if SHADOW_SCREEN_SPACE_DITHER diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh index 7d8eb9ab55..99c4b923f4 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -14,7 +14,7 @@ <@include Shadows_shared.slh@> layout(std140, binding=RENDER_UTILS_BUFFER_SHADOW_PARAMS) uniform shadowTransformBuffer { - ShadowParameters shadow; + ShadowParameters shadow; }; int getShadowCascadeCount() { @@ -30,26 +30,26 @@ float evalShadowFalloff(float depth) { } mat4 getShadowReprojection(int cascadeIndex) { - return shadow.cascades[cascadeIndex].reprojection; + return shadow.cascades[cascadeIndex].reprojection; } float getShadowScale() { - return shadow.invMapSize; + return shadow.invMapSize; } float getShadowFixedBias(int cascadeIndex) { - return shadow.cascades[cascadeIndex].fixedBias; + return shadow.cascades[cascadeIndex].fixedBias; } float getShadowSlopeBias(int cascadeIndex) { - return shadow.cascades[cascadeIndex].slopeBias; + return shadow.cascades[cascadeIndex].slopeBias; } // Compute the texture coordinates from world coordinates vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) { - vec4 shadowCoord = getShadowReprojection(cascadeIndex) * position; - return vec4(shadowCoord.xyz, 1.0); + vec4 shadowCoord = getShadowReprojection(cascadeIndex) * position; + return vec4(shadowCoord.xyz, 1.0); } bool isShadowCascadeProjectedOnPixel(vec4 cascadeTexCoords) { diff --git a/libraries/render-utils/src/Shadows_shared.slh b/libraries/render-utils/src/Shadows_shared.slh index bc8063e018..9cb46587a2 100644 --- a/libraries/render-utils/src/Shadows_shared.slh +++ b/libraries/render-utils/src/Shadows_shared.slh @@ -8,8 +8,8 @@ #define SHADOW_CASCADE_MAX_COUNT 4 struct ShadowTransform { - MAT4 reprojection; - float fixedBias; + MAT4 reprojection; + float fixedBias; float slopeBias; float _padding1; float _padding2; diff --git a/libraries/render-utils/src/debug_deferred_buffer_shared.slh b/libraries/render-utils/src/debug_deferred_buffer_shared.slh index 2d11a66d61..f5d6797c50 100644 --- a/libraries/render-utils/src/debug_deferred_buffer_shared.slh +++ b/libraries/render-utils/src/debug_deferred_buffer_shared.slh @@ -7,7 +7,7 @@ struct DebugParameters { - INT32 _shadowCascadeIndex; + INT32 _shadowCascadeIndex; }; // <@if 1@> diff --git a/libraries/render-utils/src/fxaa_blend.slf b/libraries/render-utils/src/fxaa_blend.slf index b2515ccd7f..2b9db649ca 100644 --- a/libraries/render-utils/src/fxaa_blend.slf +++ b/libraries/render-utils/src/fxaa_blend.slf @@ -44,7 +44,7 @@ void main(void) { sharpenedPixel = pixels[4]*6.8 - (pixels[1]+pixels[3]+pixels[5]+pixels[7]) - (pixels[0]+pixels[2]+pixels[6]+pixels[8])*0.7; - vec4 minColor = max(vec4(0), pixels[4]-vec4(0.5)); - vec4 maxColor = pixels[4]+vec4(0.5); + vec4 minColor = max(vec4(0), pixels[4]-vec4(0.5)); + vec4 maxColor = pixels[4]+vec4(0.5); outFragColor = clamp(pixels[4] + sharpenedPixel * params.sharpenIntensity, minColor, maxColor); } diff --git a/libraries/render-utils/src/glowLine.slf b/libraries/render-utils/src/glowLine.slf index 4e80b3358a..c65d8d6488 100644 --- a/libraries/render-utils/src/glowLine.slf +++ b/libraries/render-utils/src/glowLine.slf @@ -22,9 +22,9 @@ void main(void) { float alpha = 1.0 - abs(distanceFromCenter); // Convert from a linear alpha curve to a sharp peaked one - alpha = _color.a * pow(alpha, 10.0); - - // Drop everything where the curve falls off to nearly nothing + alpha = _color.a * pow(alpha, 10.0); + + // Drop everything where the curve falls off to nearly nothing if (alpha <= 0.05) { discard; } diff --git a/libraries/render-utils/src/model_translucent_normal_map.slf b/libraries/render-utils/src/model_translucent_normal_map.slf index 45eee9d160..7ac6982cfa 100644 --- a/libraries/render-utils/src/model_translucent_normal_map.slf +++ b/libraries/render-utils/src/model_translucent_normal_map.slf @@ -87,7 +87,7 @@ void main(void) { 1.0, occlusionTex, fragPositionES, - fragPositionWS, + fragPositionWS, albedo, fresnel, metallic, diff --git a/libraries/render-utils/src/model_translucent_normal_map_fade.slf b/libraries/render-utils/src/model_translucent_normal_map_fade.slf index 2ede2bfbaa..2c182aeb19 100644 --- a/libraries/render-utils/src/model_translucent_normal_map_fade.slf +++ b/libraries/render-utils/src/model_translucent_normal_map_fade.slf @@ -97,7 +97,7 @@ void main(void) { 1.0, occlusionTex, fragPositionES, - fragPositionWS, + fragPositionWS, albedo, fresnel, metallic, diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index c002d0c262..0a177cb58f 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -54,12 +54,12 @@ void main() { vec2 dxTexCoord = dFdx(_texCoord0) * 0.5 * taaBias; vec2 dyTexCoord = dFdy(_texCoord0) * 0.5 * taaBias; - // Perform 4x supersampling for anisotropic filtering + // Perform 4x supersampling for anisotropic filtering float a; - a = evalSDF(_texCoord0); - a += evalSDF(_texCoord0+dxTexCoord); - a += evalSDF(_texCoord0+dyTexCoord); - a += evalSDF(_texCoord0+dxTexCoord+dyTexCoord); + a = evalSDF(_texCoord0); + a += evalSDF(_texCoord0+dxTexCoord); + a += evalSDF(_texCoord0+dyTexCoord); + a += evalSDF(_texCoord0+dxTexCoord+dyTexCoord); a *= 0.25; // discard if invisible diff --git a/libraries/render-utils/src/surfaceGeometry_copyDepth.slf b/libraries/render-utils/src/surfaceGeometry_copyDepth.slf index 7eb097224e..f018ee1105 100644 --- a/libraries/render-utils/src/surfaceGeometry_copyDepth.slf +++ b/libraries/render-utils/src/surfaceGeometry_copyDepth.slf @@ -16,7 +16,7 @@ layout(binding=0) uniform sampler2D depthMap; layout(location=0) out vec4 outFragColor; void main(void) { - float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; - outFragColor = vec4(Zdb, 0.0, 0.0, 1.0); + float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; + outFragColor = vec4(Zdb, 0.0, 0.0, 1.0); } diff --git a/libraries/render-utils/src/taa.slh b/libraries/render-utils/src/taa.slh index 26ffe55263..1692c33ef6 100644 --- a/libraries/render-utils/src/taa.slh +++ b/libraries/render-utils/src/taa.slh @@ -24,10 +24,10 @@ layout(binding=RENDER_UTILS_TEXTURE_TAA_NEXT) uniform sampler2D nextMap; struct TAAParams { - float none; - float blend; - float covarianceGamma; - float debugShowVelocityThreshold; + float none; + float blend; + float covarianceGamma; + float debugShowVelocityThreshold; ivec4 flags; vec4 pixelInfo_orbZoom; vec4 regionInfo; @@ -77,47 +77,47 @@ vec2 taa_getRegionFXAA() { #define USE_YCOCG 1 vec4 taa_fetchColor(sampler2D map, vec2 uv) { - vec4 c = texture(map, uv); - // Apply rapid pseudo tonemapping as TAA is applied to a tonemapped image, using luminance as weight, as proposed in - // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf - float lum = dot(vec3(0.3,0.5,0.2),c.rgb); - c.rgb = c.rgb / (1.0+lum); + vec4 c = texture(map, uv); + // Apply rapid pseudo tonemapping as TAA is applied to a tonemapped image, using luminance as weight, as proposed in + // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf + float lum = dot(vec3(0.3,0.5,0.2),c.rgb); + c.rgb = c.rgb / (1.0+lum); #if USE_YCOCG - return vec4(color_LinearToYCoCg(c.rgb), c.a); + return vec4(color_LinearToYCoCg(c.rgb), c.a); #else - return c; + return c; #endif } vec3 taa_resolveColor(vec3 color) { #if USE_YCOCG - color = max(vec3(0), color_YCoCgToUnclampedLinear(color)); + color = max(vec3(0), color_YCoCgToUnclampedLinear(color)); #endif - // Apply rapid inverse tonemapping, using luminance as weight, as proposed in - // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf - float lum = dot(vec3(0.3,0.5,0.2),color.rgb); - color = color / (1.0-lum); - return color; + // Apply rapid inverse tonemapping, using luminance as weight, as proposed in + // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf + float lum = dot(vec3(0.3,0.5,0.2),color.rgb); + color = color / (1.0-lum); + return color; } vec4 taa_fetchSourceMap(vec2 uv) { - return taa_fetchColor(sourceMap, uv); + return taa_fetchColor(sourceMap, uv); } vec4 taa_fetchHistoryMap(vec2 uv) { - return taa_fetchColor(historyMap, uv); + return taa_fetchColor(historyMap, uv); } vec4 taa_fetchNextMap(vec2 uv) { - return taa_fetchColor(nextMap, uv); + return taa_fetchColor(nextMap, uv); } vec2 taa_fetchVelocityMap(vec2 uv) { - return texture(velocityMap, uv).xy; + return texture(velocityMap, uv).xy; } float taa_fetchDepth(vec2 uv) { - return -texture(depthMap, vec2(uv), 0).x; + return -texture(depthMap, vec2(uv), 0).x; } @@ -141,35 +141,35 @@ vec2 taa_getTexelSize() { vec3 taa_findClosestFragment3x3(vec2 uv) { - vec2 dd = abs(taa_getTexelSize()); - vec2 du = vec2(dd.x, 0.0); - vec2 dv = vec2(0.0, dd.y); + vec2 dd = abs(taa_getTexelSize()); + vec2 du = vec2(dd.x, 0.0); + vec2 dv = vec2(0.0, dd.y); - vec3 dtl = vec3(-1, -1, taa_fetchDepth(uv - dv - du)); - vec3 dtc = vec3( 0, -1, taa_fetchDepth(uv - dv)); - vec3 dtr = vec3( 1, -1, taa_fetchDepth(uv - dv + du)); + vec3 dtl = vec3(-1, -1, taa_fetchDepth(uv - dv - du)); + vec3 dtc = vec3( 0, -1, taa_fetchDepth(uv - dv)); + vec3 dtr = vec3( 1, -1, taa_fetchDepth(uv - dv + du)); - vec3 dml = vec3(-1, 0, taa_fetchDepth(uv - du)); - vec3 dmc = vec3( 0, 0, taa_fetchDepth(uv)); - vec3 dmr = vec3( 1, 0, taa_fetchDepth(uv + du)); + vec3 dml = vec3(-1, 0, taa_fetchDepth(uv - du)); + vec3 dmc = vec3( 0, 0, taa_fetchDepth(uv)); + vec3 dmr = vec3( 1, 0, taa_fetchDepth(uv + du)); - vec3 dbl = vec3(-1, 1, taa_fetchDepth(uv + dv - du)); - vec3 dbc = vec3( 0, 1, taa_fetchDepth(uv + dv)); - vec3 dbr = vec3( 1, 1, taa_fetchDepth(uv + dv + du)); + vec3 dbl = vec3(-1, 1, taa_fetchDepth(uv + dv - du)); + vec3 dbc = vec3( 0, 1, taa_fetchDepth(uv + dv)); + vec3 dbr = vec3( 1, 1, taa_fetchDepth(uv + dv + du)); - vec3 dmin = dtl; - if (ZCMP_GT(dmin.z, dtc.z)) dmin = dtc; - if (ZCMP_GT(dmin.z, dtr.z)) dmin = dtr; + vec3 dmin = dtl; + if (ZCMP_GT(dmin.z, dtc.z)) dmin = dtc; + if (ZCMP_GT(dmin.z, dtr.z)) dmin = dtr; - if (ZCMP_GT(dmin.z, dml.z)) dmin = dml; - if (ZCMP_GT(dmin.z, dmc.z)) dmin = dmc; - if (ZCMP_GT(dmin.z, dmr.z)) dmin = dmr; + if (ZCMP_GT(dmin.z, dml.z)) dmin = dml; + if (ZCMP_GT(dmin.z, dmc.z)) dmin = dmc; + if (ZCMP_GT(dmin.z, dmr.z)) dmin = dmr; - if (ZCMP_GT(dmin.z, dbl.z)) dmin = dbl; - if (ZCMP_GT(dmin.z, dbc.z)) dmin = dbc; - if (ZCMP_GT(dmin.z, dbr.z)) dmin = dbr; + if (ZCMP_GT(dmin.z, dbl.z)) dmin = dbl; + if (ZCMP_GT(dmin.z, dbc.z)) dmin = dbc; + if (ZCMP_GT(dmin.z, dbr.z)) dmin = dbr; - return vec3(uv + dd.xy * dmin.xy, dmin.z); + return vec3(uv + dd.xy * dmin.xy, dmin.z); } vec2 taa_fetchVelocityMapBest(vec2 uv) { @@ -264,8 +264,8 @@ mat3 taa_evalNeighbourColorVariance(vec3 sourceColor, vec2 fragUV, vec2 fragVelo vec2 texelSize = taa_getTexelSize(); - vec2 du = vec2(texelSize.x, 0.0); - vec2 dv = vec2(0.0, texelSize.y); + vec2 du = vec2(texelSize.x, 0.0); + vec2 dv = vec2(0.0, texelSize.y); vec3 sampleColor = taa_fetchSourceMap(fragUV - dv - du).rgb; vec3 sumSamples = sampleColor; @@ -320,72 +320,72 @@ mat3 taa_evalNeighbourColorRegion(vec3 sourceColor, vec2 fragUV, vec2 fragVeloci vec3 cmin, cmax, cavg; #if MINMAX_3X3_ROUNDED - vec2 du = vec2(texelSize.x, 0.0); - vec2 dv = vec2(0.0, texelSize.y); + vec2 du = vec2(texelSize.x, 0.0); + vec2 dv = vec2(0.0, texelSize.y); - vec3 ctl = taa_fetchSourceMap(fragUV - dv - du).rgb; - vec3 ctc = taa_fetchSourceMap(fragUV - dv).rgb; - vec3 ctr = taa_fetchSourceMap(fragUV - dv + du).rgb; - vec3 cml = taa_fetchSourceMap(fragUV - du).rgb; - vec3 cmc = sourceColor; //taa_fetchSourceMap(fragUV).rgb; // could resuse the same osurce sample isn't it ? - vec3 cmr = taa_fetchSourceMap(fragUV + du).rgb; - vec3 cbl = taa_fetchSourceMap(fragUV + dv - du).rgb; - vec3 cbc = taa_fetchSourceMap(fragUV + dv).rgb; - vec3 cbr = taa_fetchSourceMap(fragUV + dv + du).rgb; + vec3 ctl = taa_fetchSourceMap(fragUV - dv - du).rgb; + vec3 ctc = taa_fetchSourceMap(fragUV - dv).rgb; + vec3 ctr = taa_fetchSourceMap(fragUV - dv + du).rgb; + vec3 cml = taa_fetchSourceMap(fragUV - du).rgb; + vec3 cmc = sourceColor; //taa_fetchSourceMap(fragUV).rgb; // could resuse the same osurce sample isn't it ? + vec3 cmr = taa_fetchSourceMap(fragUV + du).rgb; + vec3 cbl = taa_fetchSourceMap(fragUV + dv - du).rgb; + vec3 cbc = taa_fetchSourceMap(fragUV + dv).rgb; + vec3 cbr = taa_fetchSourceMap(fragUV + dv + du).rgb; - cmin = min(ctl, min(ctc, min(ctr, min(cml, min(cmc, min(cmr, min(cbl, min(cbc, cbr)))))))); - cmax = max(ctl, max(ctc, max(ctr, max(cml, max(cmc, max(cmr, max(cbl, max(cbc, cbr)))))))); + cmin = min(ctl, min(ctc, min(ctr, min(cml, min(cmc, min(cmr, min(cbl, min(cbc, cbr)))))))); + cmax = max(ctl, max(ctc, max(ctr, max(cml, max(cmc, max(cmr, max(cbl, max(cbc, cbr)))))))); - #if MINMAX_3X3_ROUNDED || USE_YCOCG || USE_CLIPPING - cavg = (ctl + ctc + ctr + cml + cmc + cmr + cbl + cbc + cbr) / 9.0; + #if MINMAX_3X3_ROUNDED || USE_YCOCG || USE_CLIPPING + cavg = (ctl + ctc + ctr + cml + cmc + cmr + cbl + cbc + cbr) / 9.0; #elif cavg = (cmin + cmax ) * 0.5; - #endif + #endif - #if MINMAX_3X3_ROUNDED - vec3 cmin5 = min(ctc, min(cml, min(cmc, min(cmr, cbc)))); - vec3 cmax5 = max(ctc, max(cml, max(cmc, max(cmr, cbc)))); - vec3 cavg5 = (ctc + cml + cmc + cmr + cbc) / 5.0; - cmin = 0.5 * (cmin + cmin5); - cmax = 0.5 * (cmax + cmax5); - cavg = 0.5 * (cavg + cavg5); - #endif + #if MINMAX_3X3_ROUNDED + vec3 cmin5 = min(ctc, min(cml, min(cmc, min(cmr, cbc)))); + vec3 cmax5 = max(ctc, max(cml, max(cmc, max(cmr, cbc)))); + vec3 cavg5 = (ctc + cml + cmc + cmr + cbc) / 5.0; + cmin = 0.5 * (cmin + cmin5); + cmax = 0.5 * (cmax + cmax5); + cavg = 0.5 * (cavg + cavg5); + #endif #else - const float _SubpixelThreshold = 0.5; - const float _GatherBase = 0.5; - const float _GatherSubpixelMotion = 0.1666; + const float _SubpixelThreshold = 0.5; + const float _GatherBase = 0.5; + const float _GatherSubpixelMotion = 0.1666; - vec2 texel_vel = fragVelocity * imageSize; - float texel_vel_mag = length(texel_vel) * -fragZe; - float k_subpixel_motion = clamp(_SubpixelThreshold / (0.0001 + texel_vel_mag), 0.0, 1.0); - float k_min_max_support = _GatherBase + _GatherSubpixelMotion * k_subpixel_motion; + vec2 texel_vel = fragVelocity * imageSize; + float texel_vel_mag = length(texel_vel) * -fragZe; + float k_subpixel_motion = clamp(_SubpixelThreshold / (0.0001 + texel_vel_mag), 0.0, 1.0); + float k_min_max_support = _GatherBase + _GatherSubpixelMotion * k_subpixel_motion; - vec2 ss_offset01 = k_min_max_support * vec2(-texelSize.x, texelSize.y); - vec2 ss_offset11 = k_min_max_support * vec2(texelSize.x, texelSize.y); - vec3 c00 = taa_fetchSourceMap(fragUV - ss_offset11).rgb; - vec3 c10 = taa_fetchSourceMap(fragUV - ss_offset01).rgb; - vec3 c01 = taa_fetchSourceMap(fragUV + ss_offset01).rgb; - vec3 c11 = taa_fetchSourceMap(fragUV + ss_offset11).rgb; + vec2 ss_offset01 = k_min_max_support * vec2(-texelSize.x, texelSize.y); + vec2 ss_offset11 = k_min_max_support * vec2(texelSize.x, texelSize.y); + vec3 c00 = taa_fetchSourceMap(fragUV - ss_offset11).rgb; + vec3 c10 = taa_fetchSourceMap(fragUV - ss_offset01).rgb; + vec3 c01 = taa_fetchSourceMap(fragUV + ss_offset01).rgb; + vec3 c11 = taa_fetchSourceMap(fragUV + ss_offset11).rgb; - cmin = min(c00, min(c10, min(c01, c11))); - cmax = max(c00, max(c10, max(c01, c11))); + cmin = min(c00, min(c10, min(c01, c11))); + cmax = max(c00, max(c10, max(c01, c11))); cavg = (cmin + cmax ) * 0.5; - #if USE_YCOCG || USE_CLIPPING - cavg = (c00 + c10 + c01 + c11) / 4.0; + #if USE_YCOCG || USE_CLIPPING + cavg = (c00 + c10 + c01 + c11) / 4.0; #elif cavg = (cmin + cmax ) * 0.5; - #endif + #endif #endif - // shrink chroma min-max - #if USE_YCOCG - vec2 chroma_extent = vec2(0.25 * 0.5 * (cmax.r - cmin.r)); - vec2 chroma_center = sourceColor.gb; - cmin.yz = chroma_center - chroma_extent; - cmax.yz = chroma_center + chroma_extent; - cavg.yz = chroma_center; - #endif + // shrink chroma min-max + #if USE_YCOCG + vec2 chroma_extent = vec2(0.25 * 0.5 * (cmax.r - cmin.r)); + vec2 chroma_center = sourceColor.gb; + cmin.yz = chroma_center - chroma_extent; + cmax.yz = chroma_center + chroma_extent; + cavg.yz = chroma_center; + #endif return mat3(cmin, cmax, cavg); } @@ -393,22 +393,22 @@ mat3 taa_evalNeighbourColorRegion(vec3 sourceColor, vec2 fragUV, vec2 fragVeloci //#define USE_OPTIMIZATIONS 0 vec3 taa_clampColor(vec3 colorMin, vec3 colorMax, vec3 colorSource, vec3 color) { - const float eps = 0.00001; + const float eps = 0.00001; vec3 p = colorSource; vec3 q = color; - // note: only clips towards aabb center (but fast!) - vec3 p_clip = 0.5 * (colorMax + colorMin); - vec3 e_clip = 0.5 * (colorMax - colorMin) + vec3(eps); + // note: only clips towards aabb center (but fast!) + vec3 p_clip = 0.5 * (colorMax + colorMin); + vec3 e_clip = 0.5 * (colorMax - colorMin) + vec3(eps); - vec3 v_clip = q - p_clip; - vec3 v_unit = v_clip.xyz / e_clip; - vec3 a_unit = abs(v_unit); - float ma_unit = max(a_unit.x, max(a_unit.y, a_unit.z)); + vec3 v_clip = q - p_clip; + vec3 v_unit = v_clip.xyz / e_clip; + vec3 a_unit = abs(v_unit); + float ma_unit = max(a_unit.x, max(a_unit.y, a_unit.z)); - if (ma_unit > 1.0) - return p_clip + v_clip / ma_unit; - else - return q;// point inside aabb + if (ma_unit > 1.0) + return p_clip + v_clip / ma_unit; + else + return q;// point inside aabb } vec3 taa_evalConstrainColor(vec3 sourceColor, vec2 sourceUV, vec2 sourceVel, vec3 candidateColor) { @@ -416,25 +416,25 @@ vec3 taa_evalConstrainColor(vec3 sourceColor, vec2 sourceUV, vec2 sourceVel, vec colorMinMaxAvg = taa_evalNeighbourColorVariance(sourceColor, sourceUV, sourceVel); - // clamp history to neighbourhood of current sample + // clamp history to neighbourhood of current sample return taa_clampColor(colorMinMaxAvg[0], colorMinMaxAvg[1], sourceColor, candidateColor); } vec3 taa_evalFeedbackColor(vec3 sourceColor, vec3 historyColor, float blendFactor) { const float _FeedbackMin = 0.1; const float _FeedbackMax = 0.9; - // feedback weight from unbiased luminance diff (t.lottes) - #if USE_YCOCG - float lum0 = sourceColor.r; - float lum1 = historyColor.r; - #else - float lum0 = Luminance(sourceColor.rgb); - float lum1 = Luminance(historyColor.rgb); - #endif - float unbiased_diff = abs(lum0 - lum1) / max(lum0, max(lum1, 0.2)); - float unbiased_weight = 1.0 - unbiased_diff; - float unbiased_weight_sqr = unbiased_weight * unbiased_weight; - float k_feedback = mix(_FeedbackMin, _FeedbackMax, unbiased_weight_sqr); + // feedback weight from unbiased luminance diff (t.lottes) + #if USE_YCOCG + float lum0 = sourceColor.r; + float lum1 = historyColor.r; + #else + float lum0 = Luminance(sourceColor.rgb); + float lum1 = Luminance(historyColor.rgb); + #endif + float unbiased_diff = abs(lum0 - lum1) / max(lum0, max(lum1, 0.2)); + float unbiased_weight = 1.0 - unbiased_diff; + float unbiased_weight_sqr = unbiased_weight * unbiased_weight; + float k_feedback = mix(_FeedbackMin, _FeedbackMax, unbiased_weight_sqr); vec3 nextColor = mix(historyColor, sourceColor, k_feedback * blendFactor).xyz; @@ -478,7 +478,7 @@ vec3 taa_evalFXAA(vec2 fragUV) { vec3 rgbSW = texture(sourceMap, fragUV + (vec2(-1.0, +1.0) * texelSize)).xyz; vec3 rgbSE = texture(sourceMap, fragUV + (vec2(+1.0, +1.0) * texelSize)).xyz; vec3 rgbM = texture(sourceMap, fragUV).xyz; - + // convert RGB values to luminance vec3 luma = vec3(0.299, 0.587, 0.114); float lumaNW = dot(rgbNW, luma); @@ -486,11 +486,11 @@ vec3 taa_evalFXAA(vec2 fragUV) { float lumaSW = dot(rgbSW, luma); float lumaSE = dot(rgbSE, luma); float lumaM = dot( rgbM, luma); - + // luma range of local neighborhood float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); - + // direction perpendicular to local luma gradient vec2 dir; dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); @@ -502,7 +502,7 @@ vec3 taa_evalFXAA(vec2 fragUV) { float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texelSize; - + // perform additional texture sampling perpendicular to gradient vec3 rgbA = (1.0 / 2.0) * ( texture(sourceMap, fragUV + dir * (1.0 / 3.0 - 0.5)).xyz + diff --git a/libraries/render-utils/src/taa_blend.slf b/libraries/render-utils/src/taa_blend.slf index d2e23b590f..1027ddf074 100644 --- a/libraries/render-utils/src/taa_blend.slf +++ b/libraries/render-utils/src/taa_blend.slf @@ -23,7 +23,7 @@ void main(void) { // Pixel being shaded - vec3 sourceColor = texture(sourceMap, varTexCoord0).xyz; + vec3 sourceColor = texture(sourceMap, varTexCoord0).xyz; vec2 imageSize = getWidthHeight(0); vec2 texelSize = getInvWidthHeight(); @@ -124,9 +124,9 @@ void main(void) { // draw region splitter if ((abs(distToRegionDebug) < getInvWidthHeight().x) || (abs(distToRegionFXAA) < getInvWidthHeight().x)) { - outFragColor.rgb = vec3(1.0, 1.0, 0.0); - return; - } + outFragColor.rgb = vec3(1.0, 1.0, 0.0); + return; + } if (distToRegionFXAA > 0.0) { return; @@ -138,7 +138,7 @@ void main(void) { return; } - outFragColor = vec4(nextColor, 1.0); + outFragColor = vec4(nextColor, 1.0); vec3 prevColor = nextColor; diff --git a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf index 6932766d63..083440dbf8 100644 --- a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf +++ b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf @@ -27,11 +27,11 @@ void main(void) { ivec4 stereoSide; ivec2 framePixelPos = getPixelPosTexcoordPosAndSide(gl_FragCoord.xy, pixelPos, texcoordPos, stereoSide); - float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; + float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; - // The position of the pixel fragment in Eye space then in world space + // The position of the pixel fragment in Eye space then in world space vec3 eyePos = evalUnjitteredEyePositionFromZdb(stereoSide.x, Zdb, texcoordPos); - vec3 worldPos = (getViewInverse() * vec4(eyePos, 1.0)).xyz; + vec3 worldPos = (getViewInverse() * vec4(eyePos, 1.0)).xyz; vec3 prevEyePos = (getPreviousView() * vec4(worldPos, 1.0)).xyz; vec4 prevClipPos = (getUnjitteredProjection(stereoSide.x) * vec4(prevEyePos, 1.0)); diff --git a/libraries/render-utils/src/zone_drawKeyLight.slf b/libraries/render-utils/src/zone_drawKeyLight.slf index 99e9357cb0..7174914ed8 100644 --- a/libraries/render-utils/src/zone_drawKeyLight.slf +++ b/libraries/render-utils/src/zone_drawKeyLight.slf @@ -36,7 +36,7 @@ void main(void) { vec3 outSpherePos = normalize(vec3(sphereUV, -sqrt(1.0 - sphereR2))); vec3 outNormal = vec3(getViewInverse() * vec4(outSpherePos, 0.0)); - float val = step(SUN_THRESHOLD, dot(-lightDirection, outNormal)); + float val = step(SUN_THRESHOLD, dot(-lightDirection, outNormal)); color = lightIrradiance * vec3(val); @@ -45,7 +45,7 @@ void main(void) { vec3 inSpherePos = normalize(vec3(inSphereUV, sqrt(1.0 - dot(inSphereUV.xy, inSphereUV.xy)))); vec3 inNormal = vec3(getViewInverse() * vec4(inSpherePos, 0.0)); - vec3 marbleColor = max(lightIrradiance * vec3(dot(-lightDirection, inNormal)), vec3(0.01)); + vec3 marbleColor = max(lightIrradiance * vec3(dot(-lightDirection, inNormal)), vec3(0.01)); color += marbleColor; } diff --git a/libraries/render/src/render/drawItemStatus.slf b/libraries/render/src/render/drawItemStatus.slf index 309a73ae15..9409ee6171 100644 --- a/libraries/render/src/render/drawItemStatus.slf +++ b/libraries/render/src/render/drawItemStatus.slf @@ -18,7 +18,7 @@ layout(location=0) out vec4 outFragColor; layout(binding=0) uniform sampler2D _icons; vec2 getIconTexcoord(float icon, vec2 uv) { const vec2 ICON_COORD_SIZE = vec2(0.0625, 1.0); - return vec2((uv.x + icon) * ICON_COORD_SIZE.x, uv.y * ICON_COORD_SIZE.y); + return vec2((uv.x + icon) * ICON_COORD_SIZE.x, uv.y * ICON_COORD_SIZE.y); } void main(void) { From 69ae04ab095f6619ed65d4c6ac2a1b73923c7766 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 6 Sep 2018 09:03:28 -0700 Subject: [PATCH 299/744] protect Base3DOverlay::_name with a mutex --- interface/src/ui/overlays/Base3DOverlay.cpp | 20 ++++++++++++++++++-- interface/src/ui/overlays/Base3DOverlay.h | 5 +++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 6bce9d9283..767afca3e7 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -238,7 +238,9 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { */ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "name") { - return _name; + return _nameLock.resultWithReadLock([&] { + return _name; + } } if (property == "position" || property == "start" || property == "p1" || property == "point") { return vec3toVariant(getWorldPosition()); @@ -346,6 +348,20 @@ void Base3DOverlay::setVisible(bool visible) { notifyRenderVariableChange(); } +QString Base3DOverlay::getName() const { + return _nameLock.resultWithReadLock([&] { + return QString("Overlay:") + _name; + } +} + +void Base3DOverlay::setName(QString name) { + _nameLock.withWriteLock([&] { + _name = name; + }); +} + + + render::ItemKey Base3DOverlay::getKey() { auto builder = render::ItemKey::Builder(Overlay::getKey()); @@ -364,4 +380,4 @@ render::ItemKey Base3DOverlay::getKey() { } return builder.build(); -} \ No newline at end of file +} diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index d44c193055..6f6092a42e 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -29,8 +29,8 @@ public: virtual OverlayID getOverlayID() const override { return OverlayID(getID().toString()); } void setOverlayID(OverlayID overlayID) override { setID(overlayID); } - virtual QString getName() const override { return QString("Overlay:") + _name; } - void setName(QString name) { _name = name; } + virtual QString getName() const override; + void setName(QString name); // getters virtual bool is3D() const override { return true; } @@ -107,6 +107,7 @@ protected: mutable bool _renderVariableDirty { true }; QString _name; + mutable ReadWriteLockable _nameLock; }; #endif // hifi_Base3DOverlay_h From 50f4932d9cee4d3d9d74c5c75c9267b37ece9d32 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 6 Sep 2018 09:07:53 -0700 Subject: [PATCH 300/744] PR feedback: structure alignement and size --- .../entities-renderer/src/RenderablePolyVoxEntityItem.cpp | 4 ++-- libraries/entities-renderer/src/polyvox.slf | 2 +- libraries/entities-renderer/src/polyvox_fade.slf | 2 +- libraries/render-utils/src/AntialiasingEffect.cpp | 6 +++--- libraries/render-utils/src/fxaa_blend.slf | 4 ++-- libraries/render-utils/src/sdf_text3D.slf | 4 ++-- libraries/render-utils/src/sdf_text3D_transparent.slf | 4 ++-- libraries/render-utils/src/text/Font.cpp | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 292b6368bb..a4a5a34683 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1609,7 +1609,7 @@ PolyVoxEntityRenderer::PolyVoxEntityRenderer(const EntityItemPointer& entity) : _vertexFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); _vertexFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 12); }); - _params = std::make_shared(sizeof(glm::vec3), nullptr); + _params = std::make_shared(sizeof(glm::vec4), nullptr); } ShapeKey PolyVoxEntityRenderer::getShapeKey() { @@ -1673,7 +1673,7 @@ void PolyVoxEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& s void PolyVoxEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { _lastVoxelToWorldMatrix = entity->voxelToWorldMatrix(); _lastVoxelVolumeSize = entity->getVoxelVolumeSize(); - _params->setSubData(0, _lastVoxelVolumeSize); + _params->setSubData(0, vec4(_lastVoxelVolumeSize, 0.0)); graphics::MeshPointer newMesh; entity->withReadLock([&] { newMesh = entity->_mesh; diff --git a/libraries/entities-renderer/src/polyvox.slf b/libraries/entities-renderer/src/polyvox.slf index e45bdc1a6b..441dfc11e5 100644 --- a/libraries/entities-renderer/src/polyvox.slf +++ b/libraries/entities-renderer/src/polyvox.slf @@ -25,7 +25,7 @@ layout(binding=ENTITIES_TEXTURE_POLYVOX_YMAP) uniform sampler2D yMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_ZMAP) uniform sampler2D zMap; struct PolyvoxParams { - vec3 voxelVolumeSize; + vec4 voxelVolumeSize; }; layout(binding=0) uniform polyvoxParamsBuffer { diff --git a/libraries/entities-renderer/src/polyvox_fade.slf b/libraries/entities-renderer/src/polyvox_fade.slf index 0914debb3c..6e236193aa 100644 --- a/libraries/entities-renderer/src/polyvox_fade.slf +++ b/libraries/entities-renderer/src/polyvox_fade.slf @@ -28,7 +28,7 @@ layout(binding=ENTITIES_TEXTURE_POLYVOX_YMAP) uniform sampler2D yMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_ZMAP) uniform sampler2D zMap; struct PolyvoxParams { - vec3 voxelVolumeSize; + vec4 voxelVolumeSize; }; layout(binding=0) uniform polyvoxParamsBuffer { diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index b3a645caed..17c13df19a 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -90,7 +90,7 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setViewportTransform(args->_viewport); if (!_paramsBuffer) { - _paramsBuffer = std::make_shared(sizeof(glm::vec2), nullptr); + _paramsBuffer = std::make_shared(sizeof(glm::vec4), nullptr); } { @@ -109,7 +109,7 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); glm::vec2 fbExtent { args->_viewport.z, args->_viewport.w }; glm::vec2 inverseFbExtent = 1.0f / fbExtent; - _paramsBuffer->setSubData(0, inverseFbExtent); + _paramsBuffer->setSubData(0, glm::vec4(inverseFbExtent, 0.0, 0.0)); } } @@ -280,7 +280,7 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setResourceFramebufferSwapChainTexture(0, _antialiasingBuffers, 1); // Disable sharpen if FXAA if (!_blendParamsBuffer) { - _blendParamsBuffer = std::make_shared(sizeof(float), nullptr); + _blendParamsBuffer = std::make_shared(sizeof(glm::vec4), nullptr); } _blendParamsBuffer->setSubData(0, _sharpen * _params.get().regionInfo.z); batch.setUniformBuffer(0, _blendParamsBuffer); diff --git a/libraries/render-utils/src/fxaa_blend.slf b/libraries/render-utils/src/fxaa_blend.slf index 2b9db649ca..c051801659 100644 --- a/libraries/render-utils/src/fxaa_blend.slf +++ b/libraries/render-utils/src/fxaa_blend.slf @@ -20,7 +20,7 @@ layout(location=0) out vec4 outFragColor; layout(binding=0) uniform sampler2D colorTexture; struct FxaaBlendParams { - float sharpenIntensity; + vec4 sharpenIntensity; }; layout(binding=0) uniform fxaaBlendParamsBuffer { @@ -46,5 +46,5 @@ void main(void) { vec4 minColor = max(vec4(0), pixels[4]-vec4(0.5)); vec4 maxColor = pixels[4]+vec4(0.5); - outFragColor = clamp(pixels[4] + sharpenedPixel * params.sharpenIntensity, minColor, maxColor); + outFragColor = clamp(pixels[4] + sharpenedPixel * params.sharpenIntensity.x, minColor, maxColor); } diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index 0a177cb58f..d35396e469 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -17,7 +17,7 @@ layout(binding=0) uniform sampler2D Font; struct TextParams { vec4 color; - float outline; + vec4 outline; }; layout(binding=0) uniform textParamsBuffer { @@ -39,7 +39,7 @@ const float taaBias = pow(2.0, TAA_TEXTURE_LOD_BIAS); float evalSDF(vec2 texCoord) { // retrieve signed distance float sdf = textureLod(Font, texCoord, TAA_TEXTURE_LOD_BIAS).g; - if (params.outline > 0.0) { + if (params.outline.x > 0.0) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; } else { diff --git a/libraries/render-utils/src/sdf_text3D_transparent.slf b/libraries/render-utils/src/sdf_text3D_transparent.slf index d4c4fcc409..9dffca2038 100644 --- a/libraries/render-utils/src/sdf_text3D_transparent.slf +++ b/libraries/render-utils/src/sdf_text3D_transparent.slf @@ -17,7 +17,7 @@ layout(binding=0) uniform sampler2D Font; struct TextParams { vec4 color; - float outline; + vec4 outline; }; layout(binding=0) uniform textParamsBuffer { @@ -38,7 +38,7 @@ const float outlineExpansion = 0.2; void main() { // retrieve signed distance float sdf = texture(Font, _texCoord0).g; - if (params.outline > 0.0) { + if (params.outline.x > 0.0) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; } else { diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index 5ae50a8c73..68a1e59dc3 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -356,7 +356,7 @@ void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString struct GpuDrawParams { glm::vec4 color; - float outline; + glm::vec4 outline; }; if (!drawInfo.paramsBuffer || drawInfo.params.color != color || drawInfo.params.effect != effectType) { @@ -364,7 +364,7 @@ void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString drawInfo.params.effect = effectType; GpuDrawParams gpuDrawParams; gpuDrawParams.color = ColorUtils::sRGBToLinearVec4(drawInfo.params.color); - gpuDrawParams.outline = (drawInfo.params.effect == OUTLINE_EFFECT) ? 1 : 0; + gpuDrawParams.outline.x = (drawInfo.params.effect == OUTLINE_EFFECT) ? 1 : 0; drawInfo.paramsBuffer = std::make_shared(sizeof(GpuDrawParams), nullptr); drawInfo.paramsBuffer->setSubData(0, sizeof(GpuDrawParams), (const gpu::Byte*)&gpuDrawParams); } From 650f112f16bb0a2a7ae18b39be5eec65b8d31ec1 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 28 Aug 2018 17:00:27 -0700 Subject: [PATCH 301/744] Fix keyword in shader layout --- libraries/render-utils/src/simple_transparent_textured.slf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index 0e6198de68..5573a7aa22 100644 --- a/libraries/render-utils/src/simple_transparent_textured.slf +++ b/libraries/render-utils/src/simple_transparent_textured.slf @@ -17,7 +17,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(location=0) uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; From fade3a8de09b4c1f162c1cb03da3e676d8d47b2d Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 5 Sep 2018 12:39:12 -0700 Subject: [PATCH 302/744] Fixing shader implicit type conversions --- .../display-plugins/InterleavedSrgbToLinear.slf | 2 +- .../entities-renderer/src/paintStroke_fade.slf | 2 +- libraries/gpu/src/gpu/Paint.slh | 2 +- libraries/render-utils/src/BloomThreshold.slf | 4 ++-- libraries/render-utils/src/Highlight.slh | 6 +++--- libraries/render-utils/src/Shadow.slh | 10 +++++----- libraries/render-utils/src/drawWorkloadView.slv | 2 +- libraries/render-utils/src/fxaa.slf | 2 +- .../src/lightClusters_drawClusterContent.slf | 4 ++-- .../src/lightClusters_drawClusterContent.slv | 2 +- .../src/lightClusters_drawClusterFromDepth.slf | 2 +- libraries/render-utils/src/simple_fade.slf | 4 ++-- libraries/render-utils/src/ssao.slh | 16 ++++++++-------- .../render-utils/src/ssao_debugOcclusion.slf | 6 +++--- .../render-utils/src/ssao_makeOcclusion.slf | 9 +++++---- .../src/subsurfaceScattering_drawScattering.slf | 2 +- libraries/render-utils/src/taa.slh | 4 ++-- libraries/render-utils/src/taa_blend.slf | 6 +++--- libraries/render-utils/src/zone_drawAmbient.slf | 2 +- libraries/render/src/render/drawCellBounds.slv | 2 +- 20 files changed, 45 insertions(+), 44 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf b/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf index 5f7b3f3411..17dedce7f9 100644 --- a/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf +++ b/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf @@ -11,7 +11,7 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; void main(void) { - ivec2 texCoord = ivec2(floor(varTexCoord0 * textureData.textureSize)); + ivec2 texCoord = ivec2(floor(varTexCoord0 * vec2(textureData.textureSize))); texCoord.x /= 2; int row = int(floor(gl_FragCoord.y)); if (row % 2 > 0) { diff --git a/libraries/entities-renderer/src/paintStroke_fade.slf b/libraries/entities-renderer/src/paintStroke_fade.slf index 8739c9bb9b..e5f70c8038 100644 --- a/libraries/entities-renderer/src/paintStroke_fade.slf +++ b/libraries/entities-renderer/src/paintStroke_fade.slf @@ -45,7 +45,7 @@ void main(void) { int frontCondition = 1 -int(gl_FrontFacing) * 2; vec3 color = varColor.rgb; packDeferredFragmentTranslucent( - interpolatedNormal * frontCondition, + interpolatedNormal * float(frontCondition), texel.a * varColor.a, polyline.color * texel.rgb + fadeEmissive, vec3(0.01, 0.01, 0.01), diff --git a/libraries/gpu/src/gpu/Paint.slh b/libraries/gpu/src/gpu/Paint.slh index 5f49b20b30..54a1214e12 100644 --- a/libraries/gpu/src/gpu/Paint.slh +++ b/libraries/gpu/src/gpu/Paint.slh @@ -17,7 +17,7 @@ float paintStripe(float value, float offset, float scale, float edge) { float width = fwidth(value); float normalizedWidth = width * scale; - float x0 = (value + offset) * scale - normalizedWidth / 2; + float x0 = (value + offset) * scale - normalizedWidth / 2.0; float x1 = x0 + normalizedWidth; float balance = 1.0 - edge; diff --git a/libraries/render-utils/src/BloomThreshold.slf b/libraries/render-utils/src/BloomThreshold.slf index 621aa31622..47a1fb0d9f 100644 --- a/libraries/render-utils/src/BloomThreshold.slf +++ b/libraries/render-utils/src/BloomThreshold.slf @@ -30,7 +30,7 @@ void main(void) { for (int x=0 ; x } else { - vec2 halfTexel = getInvWidthHeight() / 2; + vec2 halfTexel = getInvWidthHeight() / 2.0; vec2 texCoord0 = varTexCoord0+halfTexel; float weight = 0.0; - vec2 deltaUv = params._size / params._blurKernelSize; + vec2 deltaUv = params._size / float(params._blurKernelSize); vec2 lineStartUv = texCoord0 - params._size / 2.0; vec2 uv; int x; @@ -87,7 +87,7 @@ void main(void) { } } - if (intensity > 0) { + if (intensity > 0.0) { // sumOutlineDepth /= intensity; } else { sumOutlineDepth = FAR_Z; diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index db1c3b4d85..5115a876fe 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -67,10 +67,10 @@ ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) { ivec2 offset = coords & ivec2(1,1); offset.y = (offset.x+offset.y) & 1; - offsets.points[0] = shadowScale * vec3(offset + PCFkernel[0], 0.0); - offsets.points[1] = shadowScale * vec3(offset + PCFkernel[1], 0.0); - offsets.points[2] = shadowScale * vec3(offset + PCFkernel[2], 0.0); - offsets.points[3] = shadowScale * vec3(offset + PCFkernel[3], 0.0); + offsets.points[0] = shadowScale * vec3(vec2(offset) + PCFkernel[0], 0.0); + offsets.points[1] = shadowScale * vec3(vec2(offset) + PCFkernel[1], 0.0); + offsets.points[2] = shadowScale * vec3(vec2(offset) + PCFkernel[2], 0.0); + offsets.points[3] = shadowScale * vec3(vec2(offset) + PCFkernel[3], 0.0); return offsets; } @@ -104,7 +104,7 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe vec3 cascadeMix; bvec4 isPixelOnCascade; int cascadeIndex; - float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0, 1); + float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0.0, 1.0); for (cascadeIndex=0 ; cascadeIndex lateralDir = normalize(cross(vec3(0.0, 0.0, 1.0), normalize(tanEye))); - posEye.xyz += (0.005 * abs(posEye.z) * (regionID + 1)) * (-1.0 + 2.0 * float(segmentVertexID)) * lateralDir; + posEye.xyz += (0.005 * abs(posEye.z) * float(regionID + 1)) * (-1.0 + 2.0 * float(segmentVertexID)) * lateralDir; varEyePos = posEye.xyz; <$transformEyeToClipPos(cam, posEye, gl_Position)$> diff --git a/libraries/render-utils/src/fxaa.slf b/libraries/render-utils/src/fxaa.slf index 2c5e1cdc31..f1096a3054 100644 --- a/libraries/render-utils/src/fxaa.slf +++ b/libraries/render-utils/src/fxaa.slf @@ -68,7 +68,7 @@ void main() { outFragColor.w = 1.0; }*/ - if (gl_FragCoord.x > 800) { + if (gl_FragCoord.x > 800.0) { /* // filter width limit for dependent "two-tap" texture samples float FXAA_SPAN_MAX = 8.0; diff --git a/libraries/render-utils/src/lightClusters_drawClusterContent.slf b/libraries/render-utils/src/lightClusters_drawClusterContent.slf index 65ae8f423e..27cb838566 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterContent.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterContent.slf @@ -42,7 +42,7 @@ void main(void) { ivec3 cluster = clusterGrid_getCluster(clusterIndex); int numLights = cluster.x + cluster.y; - float numLightsScale = clamp(numLights * 0.05, 0.01, 1.0); + float numLightsScale = clamp(float(numLights) * 0.05, 0.01, 1.0); int clusterOffset = cluster.z; @@ -90,6 +90,6 @@ void main(void) { numLightTouching++; } - _fragColor = vec4(colorRamp(1.0 - (numLightTouching / 12.0f)), (numLightTouching > 0 ? 0.5 + 0.5 * numLightsScale : 0.0)); + _fragColor = vec4(colorRamp(1.0 - (float(numLightTouching) / 12.0f)), (numLightTouching > 0 ? 0.5 + 0.5 * numLightsScale : 0.0)); } diff --git a/libraries/render-utils/src/lightClusters_drawClusterContent.slv b/libraries/render-utils/src/lightClusters_drawClusterContent.slv index d7e4a66a6a..1303cf3926 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterContent.slv +++ b/libraries/render-utils/src/lightClusters_drawClusterContent.slv @@ -56,7 +56,7 @@ void main(void) { ivec3 cluster = clusterGrid_getCluster(gpu_InstanceID()); int numLights = cluster.x + cluster.y; - float numLightsScale = clamp(numLights * 0.1, 0.0, 1.0); + float numLightsScale = clamp(float(numLights) * 0.1, 0.0, 1.0); ivec3 clusterPos = frustumGrid_indexToCluster(gpu_InstanceID()); diff --git a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf index 4efb60a259..abbd86dd70 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf @@ -39,7 +39,7 @@ void main(void) { ivec3 cluster = clusterGrid_getCluster(frustumGrid_clusterToIndex(clusterPos)); int numLights = cluster.x + cluster.y; - float numLightsScale = clamp(numLights * 0.05, 0.01, 1.0); + float numLightsScale = clamp(float(numLights) * 0.05, 0.01, 1.0); ivec3 dims = frustumGrid.dims.xyz; diff --git a/libraries/render-utils/src/simple_fade.slf b/libraries/render-utils/src/simple_fade.slf index e9f94c29bc..97ed0c570c 100644 --- a/libraries/render-utils/src/simple_fade.slf +++ b/libraries/render-utils/src/simple_fade.slf @@ -91,7 +91,7 @@ void main(void) { normal, 1.0, diffuse+fadeEmissive, - max(0, 1.0 - shininess / 128.0), + max(0.0, 1.0 - shininess / 128.0), DEFAULT_METALLIC, specular, specular); @@ -100,7 +100,7 @@ void main(void) { normal, 1.0, diffuse, - max(0, 1.0 - shininess / 128.0), + max(0.0, 1.0 - shininess / 128.0), length(specular), DEFAULT_EMISSIVE+fadeEmissive, DEFAULT_OCCLUSION, diff --git a/libraries/render-utils/src/ssao.slh b/libraries/render-utils/src/ssao.slh index 5341a1682a..b149d8f912 100644 --- a/libraries/render-utils/src/ssao.slh +++ b/libraries/render-utils/src/ssao.slh @@ -137,14 +137,14 @@ float getBlurCoef(int c) { float getAngleDitheringWorldPos(in vec3 pixelWorldPos) { vec3 worldPosFract = fract(pixelWorldPos * 1.0); - ivec3 pixelPos = ivec3(worldPosFract * 256); + ivec3 pixelPos = ivec3(worldPosFract * 256.0); - return isDitheringEnabled() * ((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) + (3 * pixelPos.y ^ pixelPos.z + pixelPos.x * pixelPos.z)) * 10 + getFrameDithering(); + return isDitheringEnabled() * float(((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) + (3 * pixelPos.y ^ pixelPos.z + pixelPos.x * pixelPos.z)) * 10) + getFrameDithering(); } float getAngleDithering(in ivec2 pixelPos) { // Hash function used in the AlchemyAO paper - return isDitheringEnabled() * (3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10 + getFrameDithering(); + return isDitheringEnabled() * float((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10) + getFrameDithering(); } float evalDiskRadius(float Zeye, vec2 imageSize) { @@ -162,7 +162,7 @@ const float TWO_PI = 6.28; vec3 getUnitTapLocation(int sampleNumber, float spinAngle){ // Radius relative to ssR - float alpha = float(sampleNumber + 0.5) * getInvNumSamples(); + float alpha = (float(sampleNumber) + 0.5) * getInvNumSamples(); float angle = alpha * (getNumSpiralTurns() * TWO_PI) + spinAngle; return vec3(cos(angle), sin(angle), alpha); } @@ -219,7 +219,7 @@ vec3 getTapLocationClamped(int sampleNumber, float spinAngle, float outerRadius, if (redoTap) { tap.xy = tapPos - pixelPos; tap.z = length(tap.xy); - tap.z = 0; + tap.z = 0.0; } return tap; @@ -253,7 +253,7 @@ vec3 fetchTapUnfiltered(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) { vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize; - vec2 fetchUV = vec2(tapUV.x + side.w * 0.5 * (side.x - tapUV.x), tapUV.y); + vec2 fetchUV = vec2(tapUV.x + float(side.w) * 0.5 * (float(side.x) - tapUV.x), tapUV.y); vec3 P; P.xy = tapUV; @@ -263,7 +263,7 @@ vec3 fetchTapUnfiltered(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) { } vec3 fetchTap(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) { - int mipLevel = evalMipFromRadius(tap.z * doFetchMips()); + int mipLevel = evalMipFromRadius(tap.z * float(doFetchMips())); ivec2 ssP = ivec2(tap.xy) + ssC; ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y); @@ -276,7 +276,7 @@ vec3 fetchTap(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) { ivec2 mipP = clamp(ssPFull >> mipLevel, ivec2(0), mipSize - ivec2(1)); vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize; - vec2 fetchUV = vec2(tapUV.x + side.w * 0.5 * (side.x - tapUV.x), tapUV.y); + vec2 fetchUV = vec2(tapUV.x + float(side.w) * 0.5 * (float(side.x) - tapUV.x), tapUV.y); // vec2 tapUV = (vec2(mipP) + vec2(0.5)) / vec2(mipSize); vec3 P; diff --git a/libraries/render-utils/src/ssao_debugOcclusion.slf b/libraries/render-utils/src/ssao_debugOcclusion.slf index 007fd0cd7b..ab7989e35e 100644 --- a/libraries/render-utils/src/ssao_debugOcclusion.slf +++ b/libraries/render-utils/src/ssao_debugOcclusion.slf @@ -84,15 +84,15 @@ void main(void) { float keepTapRadius = 1.0; int keepedMip = -1; bool keep = false; - - for (int i = 0; i < getNumSamples(); ++i) { + int sampleCount = int(getNumSamples()); + for (int i = 0; i < sampleCount; ++i) { vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, cursorPixelPos, imageSize); // The occluding point in camera space vec2 fragToTap = vec2(ssC) + tap.xy - fragCoord.xy; if (dot(fragToTap,fragToTap) < keepTapRadius) { keep = true; - keepedMip = evalMipFromRadius(tap.z * doFetchMips()); + keepedMip = evalMipFromRadius(tap.z * float(doFetchMips())); } vec3 tapUVZ = fetchTap(side, ssC, tap, imageSize); diff --git a/libraries/render-utils/src/ssao_makeOcclusion.slf b/libraries/render-utils/src/ssao_makeOcclusion.slf index 1b638d4270..3934b9eddc 100644 --- a/libraries/render-utils/src/ssao_makeOcclusion.slf +++ b/libraries/render-utils/src/ssao_makeOcclusion.slf @@ -52,8 +52,9 @@ void main(void) { // Accumulate the Obscurance for each samples float sum = 0.0; - for (int i = 0; i < getNumSamples(); ++i) { - vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, ssC, imageSize); + int sampleCount = int(getNumSamples()); + for (int i = 0; i < sampleCount; ++i) { + vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, vec2(ssC), imageSize); vec3 tapUVZ = fetchTap(side, ssC, tap, imageSize); @@ -68,10 +69,10 @@ void main(void) { // Bilateral box-filter over a quad for free, respecting depth edges // (the difference that this makes is subtle) if (abs(dFdx(Cp.z)) < 0.02) { - A -= dFdx(A) * ((ssC.x & 1) - 0.5); + A -= dFdx(A) * (float(ssC.x & 1) - 0.5); } if (abs(dFdy(Cp.z)) < 0.02) { - A -= dFdy(A) * ((ssC.y & 1) - 0.5); + A -= dFdy(A) * (float(ssC.y & 1) - 0.5); } diff --git a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf index 9410a2ed2b..8664fa16fd 100644 --- a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf +++ b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf @@ -80,7 +80,7 @@ vec3 drawScatteringTableUV(vec2 cursor, vec2 texcoord) { vec3 distance = vec3(0.0); for (int c = 0; c < 3; c++) { - vec2 BRDFuv = vec2(clamp(bentNdotL[c] * 0.5 + 0.5, 0.0, 1.0), clamp(2 * curvature, 0.0, 1.0)); + vec2 BRDFuv = vec2(clamp(bentNdotL[c] * 0.5 + 0.5, 0.0, 1.0), clamp(2.0 * curvature, 0.0, 1.0)); vec2 delta = BRDFuv - texcoord; distance[c] = 1.0 - dot(delta, delta); } diff --git a/libraries/render-utils/src/taa.slh b/libraries/render-utils/src/taa.slh index 1692c33ef6..2161ad9524 100644 --- a/libraries/render-utils/src/taa.slh +++ b/libraries/render-utils/src/taa.slh @@ -117,7 +117,7 @@ vec2 taa_fetchVelocityMap(vec2 uv) { } float taa_fetchDepth(vec2 uv) { - return -texture(depthMap, vec2(uv), 0).x; + return -texture(depthMap, vec2(uv), 0.0).x; } @@ -230,7 +230,7 @@ vec2 taa_fromEyeUVToFragUV(vec2 eyeUV, int stereoSide) { vec2 fragUV = eyeUV; if (isStereo()) { fragUV.x *= 0.5; - fragUV.x += stereoSide*0.5; + fragUV.x += float(stereoSide)*0.5; } return fragUV; } diff --git a/libraries/render-utils/src/taa_blend.slf b/libraries/render-utils/src/taa_blend.slf index 1027ddf074..50575a6a07 100644 --- a/libraries/render-utils/src/taa_blend.slf +++ b/libraries/render-utils/src/taa_blend.slf @@ -57,7 +57,7 @@ void main(void) { vec2 cursorVelocityDir = cursorVelocity / cursorVelocityLength; vec2 cursorVelocityNor = vec2(cursorVelocityDir.y, -cursorVelocityDir.x); - if ((dot(cursorVelocityDir, cursorToFragVec) < 0) && abs(dot(cursorVelocityNor, cursorToFragVec)) < 1.0) { + if ((dot(cursorVelocityDir, cursorToFragVec) < 0.0) && abs(dot(cursorVelocityNor, cursorToFragVec)) < 1.0) { vec3 speedColor = taa_getVelocityColorRelative(cursorToFragLength); @@ -69,7 +69,7 @@ void main(void) { float tenPercentHeight = 0.1 * imageSize.y; float centerWidth = imageSize.x * 0.5; - //vec2 nextOrbPos = vec2(centerWidth, imageSize.y - 3 * tenPercentHeight); + //vec2 nextOrbPos = vec2(centerWidth, imageSize.y - 3.0 * tenPercentHeight); vec2 nextOrbPos = cursorPos; vec2 nextOrbPosToPix = pixPos - nextOrbPos; float nextOrbPosToPixLength = length(nextOrbPosToPix); @@ -146,7 +146,7 @@ void main(void) { prevColor = texture(historyMap, prevTexCoord).xyz; } - outFragColor.xyz = mix(prevColor, vec3(1,0,1), clamp(distance(prevColor, nextColor) - 0.01, 0, 1)); + outFragColor.xyz = mix(prevColor, vec3(1,0,1), clamp(distance(prevColor, nextColor) - 0.01, 0.0, 1.0)); if (pixVelocityLength > params.debugShowVelocityThreshold) { vec3 speedColor = taa_getVelocityColorAboveThreshold(pixVelocityLength); diff --git a/libraries/render-utils/src/zone_drawAmbient.slf b/libraries/render-utils/src/zone_drawAmbient.slf index e560d91308..f20d83e913 100644 --- a/libraries/render-utils/src/zone_drawAmbient.slf +++ b/libraries/render-utils/src/zone_drawAmbient.slf @@ -44,7 +44,7 @@ void main(void) { // vec3 ambient = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(lightAmbient), fragNormal).xyz; // _fragColor = vec4( 0.5 * (fragNormal + vec3(1.0)), 1.0); - vec3 color = (sphereUV.x > 0 ? ambientMap : ambientSH); + vec3 color = (sphereUV.x > 0.0 ? ambientMap : ambientSH); color = color * 1.0 - base.w + base.xyz * base.w; const float INV_GAMMA_22 = 1.0 / 2.2; diff --git a/libraries/render/src/render/drawCellBounds.slv b/libraries/render/src/render/drawCellBounds.slv index 913a3a372a..24cc6254fd 100644 --- a/libraries/render/src/render/drawCellBounds.slv +++ b/libraries/render/src/render/drawCellBounds.slv @@ -61,5 +61,5 @@ void main(void) { TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, pos, gl_Position)$> - varColor = vec4(colorWheel(fract(float(inCellLocation.w) / 5.0)), 0.8 + 0.2 * cellIsEmpty); + varColor = vec4(colorWheel(fract(float(inCellLocation.w) / 5.0)), 0.8 + 0.2 * float(cellIsEmpty)); } \ No newline at end of file From 5a0a259c214b4a3a4fc7dc2467f3b7d3a1935159 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 6 Sep 2018 09:37:01 -0700 Subject: [PATCH 303/744] dammit --- interface/src/ui/overlays/Base3DOverlay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 767afca3e7..89e6fac3dc 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -240,7 +240,7 @@ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "name") { return _nameLock.resultWithReadLock([&] { return _name; - } + }); } if (property == "position" || property == "start" || property == "p1" || property == "point") { return vec3toVariant(getWorldPosition()); From 042044ceaf061aa9c4b6d7f1eed4965718f36ae3 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 6 Sep 2018 09:38:12 -0700 Subject: [PATCH 304/744] dammit --- interface/src/ui/overlays/Base3DOverlay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 89e6fac3dc..1d8db69e26 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -351,7 +351,7 @@ void Base3DOverlay::setVisible(bool visible) { QString Base3DOverlay::getName() const { return _nameLock.resultWithReadLock([&] { return QString("Overlay:") + _name; - } + }); } void Base3DOverlay::setName(QString name) { From 01424b81792f3e2e1a38db327f2be59f899f2851 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 6 Sep 2018 09:39:33 -0700 Subject: [PATCH 305/744] Head Collision Pick added --- .../controllers/controllerModules/teleport.js | 108 +++++++++++------- 1 file changed, 69 insertions(+), 39 deletions(-) diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 2dea25fc78..92fc91094d 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -173,38 +173,6 @@ Script.include("/~/system/libraries/controllers.js"); renderStates: teleportRenderStates, maxDistance: 8.0 }); - - this.teleportCollisionPick; - - this.recreateCollisionPick = function() { - if (_this.teleportCollisionPick !== undefined) { - Picks.removePick(_this.teleportCollisionPick); - } - - var capsuleData = MyAvatar.getCollisionCapsule(); - var capsuleHeight = Vec3.distance(capsuleData.start, capsuleData.end); - var offset = Vec3.distance(Vec3.sum(capsuleData.start, {x: 0, y: 0.5*capsuleHeight, z: 0}), MyAvatar.position); - var radius = capsuleData.radius; - var height = 2.0 * radius + capsuleHeight; - - _this.teleportCollisionPick = Picks.createPick(PickType.Collision, { - enabled: true, - parentID: Pointers.getPointerProperties(_this.teleportParabolaHandInvisible).renderStates["invisible"].end, - filter: Picks.PICK_ENTITIES + Picks.PICK_AVATARS, - shape: { - shapeType: "capsule-y", - dimensions: { - x: radius * 2.0, - y: height - (radius * 2.0), - z: radius * 2.0 - } - }, - position: { x: 0, y: offset + (height * 0.5), z: 0 }, - threshold: _this.capsuleThreshold - }); - } - - _this.recreateCollisionPick(); this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Parabola, { joint: "Avatar", @@ -232,13 +200,67 @@ Script.include("/~/system/libraries/controllers.js"); renderStates: teleportRenderStates, maxDistance: 8.0 }); + + this.teleportHeadCollisionPick; + this.teleportHandCollisionPick; + + this.recreateCollisionPicks = function() { + + if (_this.teleportHandCollisionPick !== undefined) { + Picks.removePick(_this.teleportHandCollisionPick); + } + if (_this.teleportHeadCollisionPick !== undefined) { + Picks.removePick(_this.teleportHeadCollisionPick); + } + var capsuleData = MyAvatar.getCollisionCapsule(); + var capsuleHeight = Vec3.distance(capsuleData.start, capsuleData.end); + var offset = Vec3.distance(Vec3.sum(capsuleData.start, {x: 0, y: 0.5*capsuleHeight, z: 0}), MyAvatar.position); + var radius = capsuleData.radius; + var height = 2.0 * radius + capsuleHeight; + var scale = height/2.0; + + _this.teleportHandCollisionPick = Picks.createPick(PickType.Collision, { + enabled: true, + parentID: Pointers.getPointerProperties(_this.teleportParabolaHandInvisible).renderStates["invisible"].end, + filter: Picks.PICK_ENTITIES + Picks.PICK_AVATARS, + shape: { + shapeType: "capsule-y", + dimensions: { + x: radius * 2.0, + y: height - (radius * 2.0), + z: radius * 2.0 + } + }, + position: { x: 0, y: offset + (height * 0.5), z: 0 }, + threshold: scale * _this.capsuleThreshold + }); + + _this.teleportHeadCollisionPick = Picks.createPick(PickType.Collision, { + enabled: true, + parentID: Pointers.getPointerProperties(_this.teleportParabolaHeadInvisible).renderStates["invisible"].end, + filter: Picks.PICK_ENTITIES + Picks.PICK_AVATARS, + shape: { + shapeType: "capsule-y", + dimensions: { + x: radius * 2.0, + y: height - (radius * 2.0), + z: radius * 2.0 + } + }, + position: { x: 0, y: offset + (height * 0.5), z: 0 }, + threshold: scale * _this.capsuleThreshold + }); + } + + _this.recreateCollisionPicks(); this.cleanup = function() { Pointers.removePointer(_this.teleportParabolaHandVisible); Pointers.removePointer(_this.teleportParabolaHandInvisible); Pointers.removePointer(_this.teleportParabolaHeadVisible); Pointers.removePointer(_this.teleportParabolaHeadInvisible); - Picks.removePick(_this.teleportCollisionPick); + Picks.removePick(_this.teleportHandCollisionPick); + Picks.removePick(_this.teleportHeadCollisionPick); }; this.axisButtonStateX = 0; // Left/right axis button pressed. @@ -307,13 +329,17 @@ Script.include("/~/system/libraries/controllers.js"); if (!pose.valid) { Pointers.disablePointer(_this.teleportParabolaHandVisible); Pointers.disablePointer(_this.teleportParabolaHandInvisible); + Picks.disablePick(_this.teleportHandCollisionPick); Pointers.enablePointer(_this.teleportParabolaHeadVisible); Pointers.enablePointer(_this.teleportParabolaHeadInvisible); + Picks.enablePick(_this.teleportHeadCollisionPick); } else { Pointers.enablePointer(_this.teleportParabolaHandVisible); Pointers.enablePointer(_this.teleportParabolaHandInvisible); + Picks.enablePick(_this.teleportHandCollisionPick); Pointers.disablePointer(_this.teleportParabolaHeadVisible); Pointers.disablePointer(_this.teleportParabolaHeadInvisible); + Picks.disablePick(_this.teleportHeadCollisionPick); } // We do up to 2 picks to find a teleport location. @@ -325,15 +351,17 @@ Script.include("/~/system/libraries/controllers.js"); // We might hit an invisible entity that is not a seat, so we need to do a second pass. // * In the second pass we pick against visible entities only. // - var result; + var result, collisionResult; if (mode === 'head') { result = Pointers.getPrevPickResult(_this.teleportParabolaHeadInvisible); + collisionResult = Picks.getPrevPickResult(_this.teleportHeadCollisionPick); } else { result = Pointers.getPrevPickResult(_this.teleportParabolaHandInvisible); + collisionResult = Picks.getPrevPickResult(_this.teleportHandCollisionPick); } - var collisionResult = Picks.getPrevPickResult(_this.teleportCollisionPick); var teleportLocationType = getTeleportTargetType(result, collisionResult); + if (teleportLocationType === TARGET.INVISIBLE) { if (mode === 'head') { result = Pointers.getPrevPickResult(_this.teleportParabolaHeadVisible); @@ -386,6 +414,8 @@ Script.include("/~/system/libraries/controllers.js"); Pointers.disablePointer(_this.teleportParabolaHandInvisible); Pointers.disablePointer(_this.teleportParabolaHeadVisible); Pointers.disablePointer(_this.teleportParabolaHeadInvisible); + Picks.disablePick(_this.teleportParabolaHeadInvisible); + Picks.disablePick(_this.teleportParabolaHandInvisible); }; this.setTeleportState = function(mode, visibleState, invisibleState) { @@ -610,13 +640,13 @@ Script.include("/~/system/libraries/controllers.js"); } var afterSet = new AfterSet(100, 30, function(value) { - leftTeleporter.recreateCollisionPick(); - rightTeleporter.recreateCollisionPick(); + leftTeleporter.recreateCollisionPicks(); + rightTeleporter.recreateCollisionPicks(); }); MyAvatar.onLoadComplete.connect(function () { - leftTeleporter.recreateCollisionPick(); - rightTeleporter.recreateCollisionPick(); + leftTeleporter.recreateCollisionPicks(); + rightTeleporter.recreateCollisionPicks(); }); MyAvatar.sensorToWorldScaleChanged.connect(function() { From f7226757ad0d901adf02859e20025b616dd4b058 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 6 Sep 2018 10:03:05 -0700 Subject: [PATCH 306/744] Just import TransformNode in AvatarTransformNode.h and similar --- interface/src/ui/overlays/OverlayTransformNode.h | 2 +- .../avatars-renderer/src/avatars-renderer/AvatarTransformNode.h | 2 +- libraries/entities/src/EntityTransformNode.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/overlays/OverlayTransformNode.h b/interface/src/ui/overlays/OverlayTransformNode.h index 79e6a37f45..b9ea9f72c4 100644 --- a/interface/src/ui/overlays/OverlayTransformNode.h +++ b/interface/src/ui/overlays/OverlayTransformNode.h @@ -8,7 +8,7 @@ #ifndef hifi_OverlayTransformNode_h #define hifi_OverlayTransformNode_h -#include "NestableTransformNode.h" +#include "TransformNode.h" #include "Base3DOverlay.h" diff --git a/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.h b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.h index 9036d42639..03191b8dbe 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.h +++ b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.h @@ -8,7 +8,7 @@ #ifndef hifi_AvatarTransformNode_h #define hifi_AvatarTransformNode_h -#include "NestableTransformNode.h" +#include "TransformNode.h" #include "Avatar.h" diff --git a/libraries/entities/src/EntityTransformNode.h b/libraries/entities/src/EntityTransformNode.h index fc5eeff652..b7388600df 100644 --- a/libraries/entities/src/EntityTransformNode.h +++ b/libraries/entities/src/EntityTransformNode.h @@ -8,7 +8,7 @@ #ifndef hifi_EntityTransformNode_h #define hifi_EntityTransformNode_h -#include "NestableTransformNode.h" +#include "TransformNode.h" #include "EntityItem.h" From 390e89a64ddfa8cecead7f1b2e3403d80e63939b Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 6 Sep 2018 10:20:26 -0700 Subject: [PATCH 307/744] Fix missing NestableTransformNode import --- interface/src/raypick/PickScriptingInterface.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index c3a783a72b..41dd3bb6de 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -28,6 +28,7 @@ #include "MouseTransformNode.h" #include "avatar/MyAvatarHeadTransformNode.h" #include "avatar/AvatarManager.h" +#include "NestableTransformNode.h" #include "avatars-renderer/AvatarTransformNode.h" #include "ui/overlays/OverlayTransformNode.h" #include "EntityTransformNode.h" From e8ea95e3758ec5893100d49eae30549d94dbb6c5 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 6 Sep 2018 10:21:48 -0700 Subject: [PATCH 308/744] Use elseifs in PickScriptingInterface::createTransformNode --- interface/src/raypick/PickScriptingInterface.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 41dd3bb6de..6b8a2d0d7a 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -381,14 +381,13 @@ std::shared_ptr PickScriptingInterface::createTransformNode(const NestableType nestableType = sharedNestablePointer->getNestableType(); if (nestableType == NestableType::Avatar) { return std::make_shared(std::static_pointer_cast(sharedNestablePointer), parentJointIndex); - } - if (nestableType == NestableType::Overlay) { + } else if (nestableType == NestableType::Overlay) { return std::make_shared(std::static_pointer_cast(sharedNestablePointer), parentJointIndex); - } - if (nestableType == NestableType::Entity) { + } else if (nestableType == NestableType::Entity) { return std::make_shared(std::static_pointer_cast(sharedNestablePointer), parentJointIndex); + } else { + return std::make_shared(nestablePointer, parentJointIndex); } - return std::make_shared(nestablePointer, parentJointIndex); } } From 3f7a7fb11ab1bf4877f59e93381a3df8bbf0dc56 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 6 Sep 2018 10:29:01 -0700 Subject: [PATCH 309/744] Return early from AvatarTransformNode and similar when getTransform fails --- interface/src/ui/overlays/OverlayTransformNode.cpp | 2 +- .../src/avatars-renderer/AvatarTransformNode.cpp | 2 +- libraries/entities/src/EntityTransformNode.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/overlays/OverlayTransformNode.cpp b/interface/src/ui/overlays/OverlayTransformNode.cpp index 64f5be932c..d0a6618e37 100644 --- a/interface/src/ui/overlays/OverlayTransformNode.cpp +++ b/interface/src/ui/overlays/OverlayTransformNode.cpp @@ -21,7 +21,7 @@ Transform OverlayTransformNode::getTransform() { bool success; Transform jointWorldTransform = overlay->getTransform(_jointIndex, success); if (!success) { - jointWorldTransform = Transform(); + return Transform(); } jointWorldTransform.setScale(overlay->getBounds().getScale()); diff --git a/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp index 2f198e8917..fc5ba5bc1a 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp @@ -21,7 +21,7 @@ Transform AvatarTransformNode::getTransform() { bool success; Transform jointWorldTransform = avatar->getTransform(_jointIndex, success); if (!success) { - jointWorldTransform = Transform(); + return Transform(); } jointWorldTransform.setScale(avatar->scaleForChildren()); diff --git a/libraries/entities/src/EntityTransformNode.cpp b/libraries/entities/src/EntityTransformNode.cpp index 42b60759db..5b0a690619 100644 --- a/libraries/entities/src/EntityTransformNode.cpp +++ b/libraries/entities/src/EntityTransformNode.cpp @@ -21,7 +21,7 @@ Transform EntityTransformNode::getTransform() { bool success; Transform jointWorldTransform = entity->getTransform(_jointIndex, success); if (!success) { - jointWorldTransform = Transform(); + return Transform(); } jointWorldTransform.setScale(entity->getScaledDimensions()); From 8a2096910395731344d1a2de7ecf5a07ff00012f Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Thu, 6 Sep 2018 10:38:10 -0700 Subject: [PATCH 310/744] Fix typo --- scripts/system/tablet-goto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 80b4e67c43..8fafac7685 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -127,7 +127,7 @@ return; } stories[story.id] = story; - var message = story.username + " " + story.action_string + " " + story.place_name + ". Open GOTO to join them."; + var message = story.username + " " + story.action_string + " in " + story.place_name + ". Open GOTO to join them."; Window.displayAnnouncement(message); didNotify = true; }); From 7839ba42db01d7b5b755a961830530f731602148 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 6 Sep 2018 10:37:48 -0700 Subject: [PATCH 311/744] Make the Collision Pick threshold scale with the largest dimension of the parent --- interface/src/raypick/CollisionPick.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index d183aecfeb..c21ee69b74 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -357,12 +357,14 @@ CollisionPick::CollisionPick(const PickFilter& filter, float maxDistance, bool e CollisionRegion CollisionPick::getMathematicalPick() const { CollisionRegion mathPick = _mathPick; mathPick.loaded = isLoaded(); - if (!parentTransform) { - return mathPick; - } else { - mathPick.transform = parentTransform->getTransform().worldTransform(mathPick.transform); - return mathPick; + if (parentTransform) { + Transform parentTransformValue = parentTransform->getTransform(); + mathPick.transform = parentTransformValue.worldTransform(mathPick.transform); + glm::vec3 scale = parentTransformValue.getScale(); + float largestDimension = glm::max(glm::max(scale.x, scale.y), scale.z); + mathPick.threshold *= largestDimension; } + return mathPick; } void CollisionPick::filterIntersections(std::vector& intersections) const { From ec2d89fd25bd83128f413fca0b34207e74aabde6 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 6 Sep 2018 11:16:58 -0700 Subject: [PATCH 312/744] fixing toolbar disappering --- interface/src/Application.cpp | 2 -- scripts/system/interstitialPage.js | 7 ++++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1868d6db4b..73e2fce956 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3497,8 +3497,6 @@ void Application::setIsInterstitialMode(bool interstitialMode) { bool interstitialModeEnabled = menu->isOptionChecked("Enable Interstitial"); if (_interstitialMode != interstitialMode && interstitialModeEnabled) { _interstitialMode = interstitialMode; - - DependencyManager::get()->setPinned(_interstitialMode); emit interstitialModeChanged(_interstitialMode); } } diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 86a9e744e9..c56e16d429 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -307,7 +307,6 @@ var THE_PLACE = "hifi://TheSpot-dev"; function clickedOnOverlay(overlayID, event) { - print(overlayID + " other: " + loadingToTheSpotID); if (loadingToTheSpotID === overlayID) { location.handleLookupString(THE_PLACE); } @@ -349,8 +348,10 @@ Overlays.editOverlay(loadingBarPlacard, properties); Overlays.editOverlay(loadingBarProgress, loadingBarProperties); - if (physicsEnabled && !HMD.active) { - toolbar.writeProperty("visible", true); + + Menu.setIsOptionChecked("Show Overlays", physicsEnabled); + if (!HMD.active) { + toolbar.writeProperty("visible", physicsEnabled); } resetValues(); From 385027321be1f58f7ee061adcdfa34438a22bff4 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 6 Sep 2018 11:18:35 -0700 Subject: [PATCH 313/744] fixing the eye open/closed pw image --- .../qml/LoginDialog/LinkAccountBody.qml | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index caf5ce0e53..cc682aa1ba 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -129,6 +129,7 @@ Item { } } ShortcutText { + id: forgotUsernameShortcut z: 10 anchors { leftMargin: usernameField.textFieldLabel.contentWidth + 10 @@ -149,24 +150,26 @@ Item { width: parent.width placeholderText: "Password" activeFocusOnPress: true + echoMode: TextInput.Password onFocusChanged: { root.text = ""; root.isPassword = true; } - Image { id: showPasswordImage - x: parent.width - 40 - height: parent.height - width: parent.width - (parent.width - 40) + x: parent.width - ((parent.height) * 31 / 23) + y: (parent.height - (parent.height * 16 / 23)) / 2 + width: parent.width - (parent.width - (((parent.height) * 31/23))) + height: parent.height * 16 / 23 source: "../../images/eyeOpen.svg" } Rectangle { + id: showPasswordHitbox z: 10 - x: parent.width - 40 - width: parent.width - (parent.width - 40) + x: parent.width - (parent.height * 31/16) + width: parent.width - (parent.width - (parent.height * 31/16)) height: parent.height color: "transparent" MouseArea { @@ -178,6 +181,13 @@ Item { showPassword = !showPassword; passwordField.echoMode = showPassword ? TextInput.Normal : TextInput.Password; showPasswordImage.source = showPassword ? "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg"; + showPasswordImage.width = passwordField.width - (passwordField.width - (passwordField.height * 31/23)); + showPasswordImage.x = -(showPasswordImage.width - passwordField.width); + showPasswordImage.height = showPassword ? passwordField.height : passwordField.height * 16 / 23; + showPasswordImage.y = showPassword ? 0 : (passwordField.height - showPasswordImage.height) / 2; + showPasswordHitbox.width = showPasswordImage.width; + showPasswordHitbox.x = showPasswordImage.x; + } } } @@ -186,6 +196,7 @@ Item { } ShortcutText { + id: forgotPasswordShortcut z: 10 anchors { leftMargin: passwordField.textFieldLabel.contentWidth + 10 From 5aa45b64d2b7aede39a48d3997d017742c49d21d Mon Sep 17 00:00:00 2001 From: sam gateau Date: Thu, 6 Sep 2018 11:24:13 -0700 Subject: [PATCH 314/744] Adressing build warnings --- libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp | 2 +- libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp | 5 +++-- libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp index 21553dc2c7..9fe1aa4029 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp @@ -269,7 +269,7 @@ void GLBackend::updateInput() { auto stride = _input._bufferStrides.data(); // Profile the count of buffers to update and use it to short cut the for loop - int numInvalids = _input._invalidBuffers.count(); + int numInvalids = (int) _input._invalidBuffers.count(); _stats._ISNumInputBufferChanges += numInvalids; PROFILE_COUNTER_IF_CHANGED(render_gpu, "numInputBuffersbound", int, numInvalids); diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp index 30e393630a..cedb18c87a 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp @@ -78,10 +78,11 @@ void GL41Backend::updateInput() { const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); auto& inputChannels = _input._format->getChannels(); - _stats._ISNumInputBufferChanges += _input._invalidBuffers.count(); + int numInvalids = (int)_input._invalidBuffers.count(); + _stats._ISNumInputBufferChanges += numInvalids; // Profile the count of buffers to update - PROFILE_COUNTER_IF_CHANGED(render_gpu, "numInputBuffersbound", int, _input._invalidBuffers.count()); + PROFILE_COUNTER_IF_CHANGED(render_gpu, "numInputBuffersbound", int, numInvalids); GLuint boundVBO = 0; for (auto& channelIt : inputChannels) { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp index de4989eb32..4a8bdc43f3 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp @@ -134,7 +134,7 @@ void GL45Backend::updateInput() { auto stride = _input._bufferStrides.data(); // Profile the count of buffers to update and use it to short cut the for loop - int numInvalids = _input._invalidBuffers.count(); + int numInvalids = (int) _input._invalidBuffers.count(); _stats._ISNumInputBufferChanges += numInvalids; PROFILE_COUNTER_IF_CHANGED(render_gpu, "numInputBuffersbound", int, numInvalids); From e428248472c13a67e7ea73785437379d68477327 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Thu, 6 Sep 2018 11:33:26 -0700 Subject: [PATCH 315/744] Adressing build warnings --- libraries/graphics/src/graphics/Geometry.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/graphics/src/graphics/Geometry.cpp b/libraries/graphics/src/graphics/Geometry.cpp index f71dcbe27d..5a374ae8d0 100755 --- a/libraries/graphics/src/graphics/Geometry.cpp +++ b/libraries/graphics/src/graphics/Geometry.cpp @@ -116,7 +116,6 @@ Box Mesh::evalPartBound(int partNum) const { index += part._startIndex; auto endIndex = index; endIndex += part._numIndices; - auto vertices = &_vertexBuffer.get(part._baseVertex); for (;index != endIndex; index++) { // skip primitive restart indices if ((*index) != PRIMITIVE_RESTART_INDEX) { @@ -138,7 +137,6 @@ Box Mesh::evalPartsBound(int partStart, int partEnd) const { Box partBound; auto index = _indexBuffer.cbegin() + (*part)._startIndex; auto endIndex = index + (*part)._numIndices; - //auto vertices = &_vertexBuffer.get((*part)._baseVertex); for (;index != endIndex; index++) { // skip primitive restart indices if ((*index) != (uint) PRIMITIVE_RESTART_INDEX) { From a5038850f17af34940c3ed15fc38b335d74b8c1f Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 6 Sep 2018 11:37:35 -0700 Subject: [PATCH 316/744] right align forgot user/pw shortcuts --- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index cc682aa1ba..902466270f 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -143,6 +143,9 @@ Item { linkColor: hifi.colors.blueAccent onLinkActivated: loginDialog.openUrl(link) + Component.onCompleted: { + forgotUsernameShortcut.x = root.implicitWidth - forgotUsernameShortcut.width; + } } TextField { @@ -210,6 +213,9 @@ Item { linkColor: hifi.colors.blueAccent onLinkActivated: loginDialog.openUrl(link) + Component.onCompleted: { + forgotPasswordShortcut.x = root.implicitWidth - forgotPasswordShortcut.width; + } } InfoItem { From 727b62144fb939501af03a4034850f4474f9a222 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 6 Sep 2018 12:03:13 -0700 Subject: [PATCH 317/744] Fix MyAvatarHeadTransformNode not using avatar scale --- interface/src/avatar/MyAvatarHeadTransformNode.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatarHeadTransformNode.cpp b/interface/src/avatar/MyAvatarHeadTransformNode.cpp index 9c202ba94a..1e83a17dd3 100644 --- a/interface/src/avatar/MyAvatarHeadTransformNode.cpp +++ b/interface/src/avatar/MyAvatarHeadTransformNode.cpp @@ -16,8 +16,9 @@ Transform MyAvatarHeadTransformNode::getTransform() { auto myAvatar = DependencyManager::get()->getMyAvatar(); glm::vec3 pos = myAvatar->getHeadPosition(); + glm::vec3 scale = glm::vec3(myAvatar->scaleForChildren()); glm::quat headOri = myAvatar->getHeadOrientation(); glm::quat ori = headOri * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT); - return Transform(ori, glm::vec3(1.0f), pos); + return Transform(ori, scale, pos); } \ No newline at end of file From e1aba52c239ea552201429bff57b4d396d969307 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 6 Sep 2018 12:09:44 -0700 Subject: [PATCH 318/744] Revert to statics for priority sort weights; update defaults from Andrew --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 6 +++--- libraries/avatars/src/AvatarData.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 9e38f465d3..a61f65ffb0 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -313,9 +313,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // prepare to sort const auto& cameraViews = nodeData->getViewFrustums(); PrioritySortUtil::PriorityQueue sortedAvatars(cameraViews, - /*AvatarData::_avatarSortCoefficientSize*/ 1.0f, // Suggested weights from Andrew M. - /*AvatarData::_avatarSortCoefficientCenter*/ 0.5f, - /*AvatarData::_avatarSortCoefficientAge*/ 0.25f); + AvatarData::_avatarSortCoefficientSize, + AvatarData::_avatarSortCoefficientCenter, + AvatarData::_avatarSortCoefficientAge); sortedAvatars.reserve(_end - _begin); for (auto listedNode = _begin; listedNode != _end; ++listedNode) { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 3e822fd17a..6896883f74 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2822,8 +2822,8 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f; -float AvatarData::_avatarSortCoefficientSize { 1.0f }; -float AvatarData::_avatarSortCoefficientCenter { 0.25 }; +float AvatarData::_avatarSortCoefficientSize { 4.0f }; +float AvatarData::_avatarSortCoefficientCenter { 4.0f }; float AvatarData::_avatarSortCoefficientAge { 1.0f }; QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) { From 714cfc5ef12f0cac1966306f0ec047edcf800f4b Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 6 Sep 2018 12:08:27 -0700 Subject: [PATCH 319/744] Add scaling information to CollisionRegion jsdoc and missing info to CollisionPickProperties jsdoc --- interface/src/raypick/PickScriptingInterface.cpp | 13 ++++++++++--- libraries/shared/src/RegisteredMetaTypes.h | 5 +++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 6b8a2d0d7a..b892c6da8e 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -263,9 +263,16 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti * A set of properties that can be passed to {@link Picks.createPick} to create a new Collision Pick. * @typedef {object} Picks.CollisionPickProperties -* @property {Shape} shape - The information about the collision region's size and shape. -* @property {Vec3} position - The position of the collision region. -* @property {Quat} orientation - The orientation of the collision region. +* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results. +* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR. +* @property {Shape} shape - The information about the collision region's size and shape. Dimensions are relative to a parent if defined. +* @property {Vec3} position - The position of the collision region, relative to a parent if defined. +* @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined. +* @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region, relative to a parent if defined. +* For overlay and entity parents, this is relative to the parent's largest dimension. +* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay. +* @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) +* @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. */ unsigned int PickScriptingInterface::createCollisionPick(const QVariant& properties) { QVariantMap propMap = properties.toMap(); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 02f5fa570c..79adce0a39 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -259,10 +259,11 @@ public: * A CollisionRegion defines a volume for checking collisions in the physics simulation. * @typedef {object} CollisionRegion -* @property {Shape} shape - The information about the collision region's size and shape. +* @property {Shape} shape - The information about the collision region's size and shape. Dimensions are relative to a parent if defined. * @property {Vec3} position - The position of the collision region, relative to a parent if defined. * @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined. -* @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region. +* @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region, relative to a parent if defined. +* For overlay and entity parents, this is relative to the parent's largest dimension. * @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay. * @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) * @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. From 6ac38190c1db0150dca9acd423cbb3dd7f80add6 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 6 Sep 2018 12:23:38 -0700 Subject: [PATCH 320/744] tweak scaling multiple --- scripts/system/libraries/entitySelectionTool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 2478160293..945f6a88d7 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -437,7 +437,7 @@ SelectionDisplay = (function() { var STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.02; var STRETCH_MINIMUM_DIMENSION = 0.001; var STRETCH_ALL_MINIMUM_DIMENSION = 0.01; - var STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE = 6; + var STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE = 2; var STRETCH_PANEL_WIDTH = 0.01; var SCALE_EDGE_OFFSET = 0.5; From 90a51e618134faf605074afb2785d0962dfa4121 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 6 Sep 2018 13:12:48 -0700 Subject: [PATCH 321/744] added walk velocity threshold, this determines if we are in the walk state. when we are then we don't do cg leaning calculation. this will smooth out the walking in a large tracked area --- interface/src/avatar/MyAvatar.cpp | 59 +++++++++++++----------- interface/src/avatar/MyAvatar.h | 2 + interface/src/avatar/MySkeletonModel.cpp | 2 +- libraries/shared/src/AvatarConstants.h | 7 +-- 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8a29d3f9c7..c8af792d8f 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -516,8 +516,8 @@ void MyAvatar::update(float deltaTime) { head->relax(deltaTime); updateFromTrackers(deltaTime); - if (_isInWalkingState && glm::length(getControllerPoseInAvatarFrame(controller::Action::HEAD).getVelocity()) < 0.15f) { - _isInWalkingState = false; + if (getIsInWalkingState() && glm::length(getControllerPoseInAvatarFrame(controller::Action::HEAD).getVelocity()) < DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { + setIsInWalkingState(false); } // Get audio loudness data from audio input device @@ -3806,6 +3806,10 @@ float MyAvatar::getUserEyeHeight() const { return userHeight - userHeight * ratio; } +bool MyAvatar::getIsInWalkingState() const { + return _isInWalkingState; +} + float MyAvatar::getWalkSpeed() const { return _walkSpeed.get() * _walkSpeedScalar; } @@ -3822,6 +3826,10 @@ void MyAvatar::setSprintMode(bool sprint) { _walkSpeedScalar = sprint ? _sprintSpeed.get() : AVATAR_WALK_SPEED_SCALAR; } +void MyAvatar::setIsInWalkingState(bool isWalking) { + _isInWalkingState = isWalking; +} + void MyAvatar::setWalkSpeed(float value) { _walkSpeed.set(value); } @@ -4006,37 +4014,38 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); - qCDebug(interfaceapp) << "currentVelocity is " < 0.15f) { - myAvatar._isInWalkingState = true; - } - } else { - glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); - glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); - glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); - float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); - if (!isActive(Horizontal) && - (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { - myAvatar.setResetMode(true); - qCDebug(interfaceapp) << "failsafe called, default back " << anatomicalHeadToHipsDistance << " scale " << myAvatar.getAvatarScale() << " current length " << glm::length(currentHeadPosition - defaultHipsPosition); + // a step is detected stepDetected = true; - if (glm::length(currentHeadPose.velocity) > 0.15f) { - myAvatar._isInWalkingState = true; + if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { + myAvatar.setIsInWalkingState(true); + } + } else { + glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); + glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); + glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); + float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); + if (!isActive(Horizontal) && + (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { + myAvatar.setResetMode(true); + stepDetected = true; + if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { + myAvatar.setIsInWalkingState(true); + } } } - qCDebug(interfaceapp) << "current head height " << currentHeadPose.getTranslation().y << " scale " << myAvatar.getAvatarScale() << " anatomical " << anatomicalHeadToHipsDistance; } return stepDetected; } @@ -4053,9 +4062,6 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { - const float VELOCITY_THRESHHOLD = 1.0f; - float currentVelocity = glm::length(myAvatar.getLocalVelocity() / myAvatar.getSensorToWorldScale()); - if (myAvatar.getHMDLeanRecenterEnabled() && qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { @@ -4063,7 +4069,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } if (myAvatar.getCenterOfGravityModelEnabled()) { - if ((!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) || ( isActive(Horizontal) && (currentVelocity > VELOCITY_THRESHHOLD))) { + if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { activate(Horizontal); if (myAvatar.getEnableStepResetRotation()) { activate(Rotation); @@ -4089,7 +4095,6 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat setForceActivateRotation(false); } if (!isActive(Horizontal) && getForceActivateHorizontal()) { - qCDebug(interfaceapp) << "called the recentering from script"; activate(Horizontal); setForceActivateHorizontal(false); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index aa76e29b14..5008190c33 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1086,6 +1086,8 @@ public: const QUuid& getSelfID() const { return AVATAR_SELF_ID; } + void setIsInWalkingState(bool isWalking); + bool getIsInWalkingState() const; void setWalkSpeed(float value); float getWalkSpeed() const; void setWalkBackwardSpeed(float value); diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 77d1a87195..3084542472 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -46,7 +46,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { } glm::mat4 hipsMat; - if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying) { + if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState())) { // then we use center of gravity model hipsMat = myAvatar->deriveBodyUsingCgModel(); } else { diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 80676b6a51..6c38d08c96 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -25,9 +25,9 @@ const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f; const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.12f; const float DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD = 0.10f; -const float DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD = 0.07f; -const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.06f; -const float DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 0.4f; +const float DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD = 0.04f; +const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.05f; +const float DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 0.3f; const float DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD = -0.02f; const float DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD = 0.4f; const float DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 3.3f; @@ -68,6 +68,7 @@ const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { -0.4016716778278351f, 0.915461599 const float DEFAULT_AVATAR_MAX_WALKING_SPEED = 2.6f; // meters / second const float DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED = 2.2f; // meters / second const float DEFAULT_AVATAR_MAX_FLYING_SPEED = 30.0f; // meters / second +const float DEFAULT_AVATAR_WALK_SPEED_THRESHOLD = 0.15f; const float DEFAULT_AVATAR_GRAVITY = -5.0f; // meters / second^2 const float DEFAULT_AVATAR_JUMP_SPEED = 3.5f; // meters / second From c986d55d8b9050eda0c1a48031ccd62d5f3ac5e1 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 6 Sep 2018 13:18:54 -0700 Subject: [PATCH 322/744] removed step script from repo --- scripts/developer/objectOrientedStep.js | 688 ------------------------ 1 file changed, 688 deletions(-) delete mode 100644 scripts/developer/objectOrientedStep.js diff --git a/scripts/developer/objectOrientedStep.js b/scripts/developer/objectOrientedStep.js deleted file mode 100644 index a5c27e36b9..0000000000 --- a/scripts/developer/objectOrientedStep.js +++ /dev/null @@ -1,688 +0,0 @@ -/* jslint bitwise: true */ - -/* global Script, Vec3, MyAvatar, Tablet, Messages, Quat, -DebugDraw, Mat4, Entities, Xform, Controller, Camera, console, document*/ - -Script.registerValue("STEPAPP", true); -var CENTIMETERSPERMETER = 100.0; -var LEFT = 0; -var RIGHT = 1; -var INCREASING = 1.0; -var DECREASING = -1.0; -var DEFAULT_AVATAR_HEIGHT = 1.64; -var TABLET_BUTTON_NAME = "STEP"; -var CHANGE_OF_BASIS_ROTATION = { x: 0, y: 1, z: 0, w: 0 }; -// in meters (mostly) -var DEFAULT_ANTERIOR = 0.04; -var DEFAULT_POSTERIOR = 0.06; -var DEFAULT_LATERAL = 0.10; -var DEFAULT_HEIGHT_DIFFERENCE = 0.02; -var DEFAULT_ANGULAR_VELOCITY = 0.3; -var DEFAULT_HAND_VELOCITY = 0.4; -var DEFAULT_ANGULAR_HAND_VELOCITY = 3.3; -var DEFAULT_HEAD_VELOCITY = 0.14; -var DEFAULT_LEVEL_PITCH = 7; -var DEFAULT_LEVEL_ROLL = 7; -var DEFAULT_DIFF = 0.0; -var DEFAULT_DIFF_EULERS = { x: 0.0, y: 0.0, z: 0.0 }; -var DEFAULT_HIPS_POSITION; -var DEFAULT_HEAD_POSITION; -var DEFAULT_TORSO_LENGTH; -var SPINE_STRETCH_LIMIT = 0.02; - -var VELOCITY_EPSILON = 0.02; -var AVERAGING_RATE = 0.03; -var HEIGHT_AVERAGING_RATE = 0.01; -var STEP_TIME_SECS = 0.2; -var MODE_SAMPLE_LENGTH = 100; -var RESET_MODE = false; -var HEAD_TURN_THRESHOLD = 25.0; -var NO_SHARED_DIRECTION = -0.98; -var LOADING_DELAY = 500; -var FAILSAFE_TIMEOUT = 2.5; - -var debugDrawBase = true; -var activated = false; -var documentLoaded = false; -var failsafeFlag = false; -var failsafeSignalTimer = -1.0; -var stepTimer = -1.0; - - -var modeArray = new Array(MODE_SAMPLE_LENGTH); -var modeHeight = -10.0; - -var handPosition; -var handOrientation; -var hands = []; -var hipToHandAverage = []; -var handDotHead = []; -var headAverageOrientation = MyAvatar.orientation; -var headPoseAverageOrientation = { x: 0, y: 0, z: 0, w: 1 }; -var averageHeight = 1.0; -var headEulers = { x: 0.0, y: 0.0, z: 0.0 }; -var headAverageEulers = { x: 0.0, y: 0.0, z: 0.0 }; -var headAveragePosition = { x: 0, y: 0.4, z: 0 }; -var frontLeft = { x: -DEFAULT_LATERAL, y: 0, z: -DEFAULT_ANTERIOR }; -var frontRight = { x: DEFAULT_LATERAL, y: 0, z: -DEFAULT_ANTERIOR }; -var backLeft = { x: -DEFAULT_LATERAL, y: 0, z: DEFAULT_POSTERIOR }; -var backRight = { x: DEFAULT_LATERAL, y: 0, z: DEFAULT_POSTERIOR }; - - -// define state readings constructor -function StateReading(headPose, rhandPose, lhandPose, backLength, diffFromMode, diffFromAverageHeight, diffFromAveragePosition, - diffFromAverageEulers) { - this.headPose = headPose; - this.rhandPose = rhandPose; - this.lhandPose = lhandPose; - this.backLength = backLength; - this.diffFromMode = diffFromMode; - this.diffFromAverageHeight = diffFromAverageHeight; - this.diffFromAveragePosition = diffFromAveragePosition; - this.diffFromAverageEulers = diffFromAverageEulers; -} - -// define current state readings object for holding tracker readings and current differences from averages -var currentStateReadings = new StateReading(Controller.getPoseValue(Controller.Standard.Head), - Controller.getPoseValue(Controller.Standard.RightHand), Controller.getPoseValue(Controller.Standard.LeftHand), - DEFAULT_TORSO_LENGTH, DEFAULT_DIFF, DEFAULT_DIFF, DEFAULT_DIFF, DEFAULT_DIFF_EULERS); - -// declare the checkbox constructor -function AppCheckbox(type,id,eventType,isChecked) { - this.type = type; - this.id = id; - this.eventType = eventType; - this.data = {value: isChecked}; -} - -// define the checkboxes in the html file -var usingAverageHeight = new AppCheckbox("checkboxtick", "runningAverageHeightCheck", "onRunningAverageHeightCheckBox", - false); -var usingModeHeight = new AppCheckbox("checkboxtick","modeCheck","onModeCheckBox",true); -var usingBaseOfSupport = new AppCheckbox("checkboxtick","baseOfSupportCheck","onBaseOfSupportCheckBox",true); -var usingAverageHeadPosition = new AppCheckbox("checkboxtick", "headAveragePositionCheck", "onHeadAveragePositionCheckBox", - false); - -var checkBoxArray = new Array(usingAverageHeight,usingModeHeight,usingBaseOfSupport,usingAverageHeadPosition); - -// declare the html slider constructor -function AppProperty(name, type, eventType, signalType, setFunction, initValue, convertToThreshold, convertToSlider, signalOn) { - this.name = name; - this.type = type; - this.eventType = eventType; - this.signalType = signalType; - this.setValue = setFunction; - this.value = initValue; - this.get = function () { - return this.value; - }; - this.convertToThreshold = convertToThreshold; - this.convertToSlider = convertToSlider; - this.signalOn = signalOn; -} - -// define the sliders -var frontBaseProperty = new AppProperty("#anteriorBase-slider", "slider", "onAnteriorBaseSlider", "frontSignal", - setAnteriorDistance, DEFAULT_ANTERIOR, function (num) { - return convertToMeters(num); - }, function (num) { - return convertToCentimeters(num); - },true); -var backBaseProperty = new AppProperty("#posteriorBase-slider", "slider", "onPosteriorBaseSlider", "backSignal", - setPosteriorDistance, DEFAULT_POSTERIOR, function (num) { - return convertToMeters(num); - }, function (num) { - return convertToCentimeters(num); - }, true); -var lateralBaseProperty = new AppProperty("#lateralBase-slider", "slider", "onLateralBaseSlider", "lateralSignal", - setLateralDistance, DEFAULT_LATERAL, function (num) { - return convertToMeters(num); - }, function (num) { - return convertToCentimeters(num); - }, true); -var headAngularVelocityProperty = new AppProperty("#angularVelocityHead-slider", "slider", "onAngularVelocitySlider", - "angularHeadSignal", setAngularThreshold, DEFAULT_ANGULAR_VELOCITY, function (num) { - var base = 4; - var shift = 2; - return convertExponential(base, num, DECREASING, shift); - }, function (num) { - var base = 4; - var shift = 2; - return convertLog(base, num, DECREASING, shift); - }, true); -var heightDifferenceProperty = new AppProperty("#heightDifference-slider", "slider", "onHeightDifferenceSlider", "heightSignal", - setHeightThreshold, DEFAULT_HEIGHT_DIFFERENCE, function (num) { - return convertToMeters(-num); - }, function (num) { - return convertToCentimeters(-num); - }, true); -var handsVelocityProperty = new AppProperty("#handsVelocity-slider", "slider", "onHandsVelocitySlider", "handVelocitySignal", - setHandVelocityThreshold, DEFAULT_HAND_VELOCITY, function (num) { - return num; - }, function (num) { - return num; - }, true); -var handsAngularVelocityProperty = new AppProperty("#handsAngularVelocity-slider", "slider", "onHandsAngularVelocitySlider", - "handAngularSignal", setHandAngularVelocityThreshold, DEFAULT_ANGULAR_HAND_VELOCITY, function (num) { - var base = 7; - var shift = 2; - return convertExponential(base, num, DECREASING, shift); - }, function (num) { - var base = 7; - var shift = 2; - return convertLog(base, num, DECREASING, shift); - }, true); -var headVelocityProperty = new AppProperty("#headVelocity-slider", "slider", "onHeadVelocitySlider", "headVelocitySignal", - setHeadVelocityThreshold, DEFAULT_HEAD_VELOCITY, function (num) { - var base = 2; - var shift = 0; - return convertExponential(base, num, INCREASING, shift); - }, function (num) { - var base = 2; - var shift = 0; - return convertLog(base, num, INCREASING, shift); - }, true); -var headPitchProperty = new AppProperty("#headPitch-slider", "slider", "onHeadPitchSlider", "headPitchSignal", - setHeadPitchThreshold, DEFAULT_LEVEL_PITCH, function (num) { - var base = 2.5; - var shift = 5; - return convertExponential(base, num, DECREASING, shift); - }, function (num) { - var base = 2.5; - var shift = 5; - return convertLog(base, num, DECREASING, shift); - }, true); -var headRollProperty = new AppProperty("#headRoll-slider", "slider", "onHeadRollSlider", "headRollSignal", setHeadRollThreshold, - DEFAULT_LEVEL_ROLL, function (num) { - var base = 2.5; - var shift = 5; - return convertExponential(base, num, DECREASING, shift); - }, function (num) { - var base = 2.5; - var shift = 5; - return convertLog(base, num, DECREASING, shift); - }, true); - -var propArray = new Array(frontBaseProperty, backBaseProperty, lateralBaseProperty, headAngularVelocityProperty, - heightDifferenceProperty, handsVelocityProperty, handsAngularVelocityProperty, headVelocityProperty, headPitchProperty, - headRollProperty); - -// var HTML_URL = Script.resolvePath("http://hifi-content.s3.amazonaws.com/angus/stepApp/stepApp.html"); -var HTML_URL = Script.resolvePath("http://hifi-content.s3.amazonaws.com/angus/stepApp/stepAppExtra.html"); -var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - -function manageClick() { - if (activated) { - tablet.gotoHomeScreen(); - } else { - tablet.gotoWebScreen(HTML_URL); - } -} - -var tabletButton = tablet.addButton({ - text: TABLET_BUTTON_NAME, - icon: Script.resolvePath("http://hifi-content.s3.amazonaws.com/angus/stepApp/foot.svg"), - activeIcon: Script.resolvePath("http://hifi-content.s3.amazonaws.com/angus/stepApp/foot.svg") -}); - -function drawBase() { - // transform corners into world space, for rendering. - var worldPointLf = Vec3.sum(MyAvatar.position,Vec3.multiplyQbyV(MyAvatar.orientation, frontLeft)); - var worldPointRf = Vec3.sum(MyAvatar.position,Vec3.multiplyQbyV(MyAvatar.orientation, frontRight)); - var worldPointLb = Vec3.sum(MyAvatar.position,Vec3.multiplyQbyV(MyAvatar.orientation, backLeft)); - var worldPointRb = Vec3.sum(MyAvatar.position,Vec3.multiplyQbyV(MyAvatar.orientation, backRight)); - - var GREEN = { r: 0, g: 1, b: 0, a: 1 }; - // draw border - DebugDraw.drawRay(worldPointLf, worldPointRf, GREEN); - DebugDraw.drawRay(worldPointRf, worldPointRb, GREEN); - DebugDraw.drawRay(worldPointRb, worldPointLb, GREEN); - DebugDraw.drawRay(worldPointLb, worldPointLf, GREEN); -} - -function onKeyPress(event) { - if (event.text === "'") { - // when the sensors are reset, then reset the mode. - RESET_MODE = false; - } -} - -function onWebEventReceived(msg) { - var message = JSON.parse(msg); - print(" we have a message from html dialog " + message.type); - propArray.forEach(function (prop) { - if (prop.eventType === message.type) { - prop.setValue(prop.convertToThreshold(message.data.value)); - print("message from " + prop.name); - // break; - } - }); - checkBoxArray.forEach(function(cbox) { - if (cbox.eventType === message.type) { - cbox.data.value = message.data.value; - // break; - } - }); - if (message.type === "onCreateStepApp") { - print("document loaded"); - documentLoaded = true; - Script.setTimeout(initAppForm, LOADING_DELAY); - } -} - -function initAppForm() { - print("step app is loaded: " + documentLoaded); - if (documentLoaded === true) { - propArray.forEach(function (prop) { - tablet.emitScriptEvent(JSON.stringify({ - "type": "trigger", - "id": prop.signalType, - "data": { "value": "green" } - })); - tablet.emitScriptEvent(JSON.stringify({ - "type": "slider", - "id": prop.name, - "data": { "value": prop.convertToSlider(prop.value) } - })); - }); - checkBoxArray.forEach(function (cbox) { - tablet.emitScriptEvent(JSON.stringify({ - "type": "checkboxtick", - "id": cbox.id, - "data": { "value": cbox.data.value } - })); - }); - } -} - -function updateSignalColors() { - - // force the updates by running the threshold comparisons - withinBaseOfSupport(currentStateReadings.headPose.translation); - withinThresholdOfStandingHeightMode(currentStateReadings.diffFromMode); - headAngularVelocityBelowThreshold(currentStateReadings.headPose.angularVelocity); - handDirectionMatchesHeadDirection(currentStateReadings.lhandPose, currentStateReadings.rhandPose); - handAngularVelocityBelowThreshold(currentStateReadings.lhandPose, currentStateReadings.rhandPose); - headVelocityGreaterThanThreshold(Vec3.length(currentStateReadings.headPose.velocity)); - headMovedAwayFromAveragePosition(currentStateReadings.diffFromAveragePosition); - headLowerThanHeightAverage(currentStateReadings.diffFromAverageHeight); - isHeadLevel(currentStateReadings.diffFromAverageEulers); - - propArray.forEach(function (prop) { - if (prop.signalOn) { - tablet.emitScriptEvent(JSON.stringify({ "type": "trigger", "id": prop.signalType, "data": { "value": "green" } })); - } else { - tablet.emitScriptEvent(JSON.stringify({ "type": "trigger", "id": prop.signalType, "data": { "value": "red" } })); - } - }); -} - -function onScreenChanged(type, url) { - print("Screen changed"); - if (type === "Web" && url === HTML_URL) { - if (!activated) { - // hook up to event bridge - tablet.webEventReceived.connect(onWebEventReceived); - print("after connect web event"); - MyAvatar.hmdLeanRecenterEnabled = false; - - } - activated = true; - } else { - if (activated) { - // disconnect from event bridge - tablet.webEventReceived.disconnect(onWebEventReceived); - documentLoaded = false; - } - activated = false; - } -} - -function getLog(x, y) { - return Math.log(y) / Math.log(x); -} - -function noConversion(num) { - return num; -} - -function convertLog(base, num, direction, shift) { - return direction * getLog(base, (num + 1.0)) + shift; -} - -function convertExponential(base, num, direction, shift) { - return Math.pow(base, (direction*num + shift)) - 1.0; -} - -function convertToCentimeters(num) { - return num * CENTIMETERSPERMETER; -} - -function convertToMeters(num) { - print("convert to meters " + num); - return num / CENTIMETERSPERMETER; -} - -function isInsideLine(a, b, c) { - return (((b.x - a.x)*(c.z - a.z) - (b.z - a.z)*(c.x - a.x)) > 0); -} - -function setAngularThreshold(num) { - headAngularVelocityProperty.value = num; - print("angular threshold " + headAngularVelocityProperty.get()); -} - -function setHeadRollThreshold(num) { - headRollProperty.value = num; - print("head roll threshold " + headRollProperty.get()); -} - -function setHeadPitchThreshold(num) { - headPitchProperty.value = num; - print("head pitch threshold " + headPitchProperty.get()); -} - -function setHeightThreshold(num) { - heightDifferenceProperty.value = num; - print("height threshold " + heightDifferenceProperty.get()); -} - -function setLateralDistance(num) { - lateralBaseProperty.value = num; - frontLeft.x = -lateralBaseProperty.get(); - frontRight.x = lateralBaseProperty.get(); - backLeft.x = -lateralBaseProperty.get(); - backRight.x = lateralBaseProperty.get(); - print("lateral distance " + lateralBaseProperty.get()); -} - -function setAnteriorDistance(num) { - frontBaseProperty.value = num; - frontLeft.z = -frontBaseProperty.get(); - frontRight.z = -frontBaseProperty.get(); - print("anterior distance " + frontBaseProperty.get()); -} - -function setPosteriorDistance(num) { - backBaseProperty.value = num; - backLeft.z = backBaseProperty.get(); - backRight.z = backBaseProperty.get(); - print("posterior distance " + backBaseProperty.get()); -} - -function setHandAngularVelocityThreshold(num) { - handsAngularVelocityProperty.value = num; - print("hand angular velocity threshold " + handsAngularVelocityProperty.get()); -} - -function setHandVelocityThreshold(num) { - handsVelocityProperty.value = num; - print("hand velocity threshold " + handsVelocityProperty.get()); -} - -function setHeadVelocityThreshold(num) { - headVelocityProperty.value = num; - print("headvelocity threshold " + headVelocityProperty.get()); -} - -function withinBaseOfSupport(pos) { - var userScale = 1.0; - frontBaseProperty.signalOn = !(isInsideLine(Vec3.multiply(userScale, frontLeft), Vec3.multiply(userScale, frontRight), pos)); - backBaseProperty.signalOn = !(isInsideLine(Vec3.multiply(userScale, backRight), Vec3.multiply(userScale, backLeft), pos)); - lateralBaseProperty.signalOn = !(isInsideLine(Vec3.multiply(userScale, frontRight), Vec3.multiply(userScale, backRight), pos) - && isInsideLine(Vec3.multiply(userScale, backLeft), Vec3.multiply(userScale, frontLeft), pos)); - return (!frontBaseProperty.signalOn && !backBaseProperty.signalOn && !lateralBaseProperty.signalOn); -} - -function withinThresholdOfStandingHeightMode(heightDiff) { - if (usingModeHeight.data.value) { - heightDifferenceProperty.signalOn = heightDiff < heightDifferenceProperty.get(); - return heightDifferenceProperty.signalOn; - } else { - return true; - } -} - -function headAngularVelocityBelowThreshold(headAngularVelocity) { - var angVel = Vec3.length({ x: headAngularVelocity.x, y: 0, z: headAngularVelocity.z }); - headAngularVelocityProperty.signalOn = angVel < headAngularVelocityProperty.get(); - return headAngularVelocityProperty.signalOn; -} - -function handDirectionMatchesHeadDirection(lhPose, rhPose) { - handsVelocityProperty.signalOn = ((handsVelocityProperty.get() < NO_SHARED_DIRECTION) || - ((!lhPose.valid || ((handDotHead[LEFT] > handsVelocityProperty.get()) && - (Vec3.length(lhPose.velocity) > VELOCITY_EPSILON))) && - (!rhPose.valid || ((handDotHead[RIGHT] > handsVelocityProperty.get()) && - (Vec3.length(rhPose.velocity) > VELOCITY_EPSILON))))); - return handsVelocityProperty.signalOn; -} - -function handAngularVelocityBelowThreshold(lhPose, rhPose) { - var xzRHandAngularVelocity = Vec3.length({ x: rhPose.angularVelocity.x, y: 0.0, z: rhPose.angularVelocity.z }); - var xzLHandAngularVelocity = Vec3.length({ x: lhPose.angularVelocity.x, y: 0.0, z: lhPose.angularVelocity.z }); - handsAngularVelocityProperty.signalOn = ((!rhPose.valid ||(xzRHandAngularVelocity < handsAngularVelocityProperty.get())) - && (!lhPose.valid || (xzLHandAngularVelocity < handsAngularVelocityProperty.get()))); - return handsAngularVelocityProperty.signalOn; -} - -function headVelocityGreaterThanThreshold(headVel) { - headVelocityProperty.signalOn = (headVel > headVelocityProperty.get()) || (headVelocityProperty.get() < VELOCITY_EPSILON); - return headVelocityProperty.signalOn; -} - -function headMovedAwayFromAveragePosition(headDelta) { - return !withinBaseOfSupport(headDelta) || !usingAverageHeadPosition.data.value; -} - -function headLowerThanHeightAverage(heightDiff) { - if (usingAverageHeight.data.value) { - print("head lower than height average"); - heightDifferenceProperty.signalOn = heightDiff < heightDifferenceProperty.get(); - return heightDifferenceProperty.signalOn; - } else { - return true; - } -} - -function isHeadLevel(diffEulers) { - headRollProperty.signalOn = Math.abs(diffEulers.z) < headRollProperty.get(); - headPitchProperty.signalOn = Math.abs(diffEulers.x) < headPitchProperty.get(); - return (headRollProperty.signalOn && headPitchProperty.signalOn); -} - -function findAverage(arr) { - var sum = arr.reduce(function (acc, val) { - return acc + val; - },0); - return sum / arr.length; -} - -function addToModeArray(arr,num) { - for (var i = 0 ;i < (arr.length - 1); i++) { - arr[i] = arr[i+1]; - } - arr[arr.length - 1] = (Math.floor(num*CENTIMETERSPERMETER))/CENTIMETERSPERMETER; -} - -function findMode(ary, currentMode, backLength, defaultBack, currentHeight) { - var numMapping = {}; - var greatestFreq = 0; - var mode; - ary.forEach(function (number) { - numMapping[number] = (numMapping[number] || 0) + 1; - if ((greatestFreq < numMapping[number]) || ((numMapping[number] === MODE_SAMPLE_LENGTH) && (number > currentMode) )) { - greatestFreq = numMapping[number]; - mode = number; - } - }); - if (mode > currentMode) { - return Number(mode); - } else { - if (!RESET_MODE && HMD.active) { - print("resetting the mode............................................. "); - print("resetting the mode............................................. "); - RESET_MODE = true; - var correction = 0.02; - return currentHeight - correction; - } else { - return currentMode; - } - } -} - -function update(dt) { - if (debugDrawBase) { - drawBase(); - } - // Update current state information - currentStateReadings.headPose = Controller.getPoseValue(Controller.Standard.Head); - currentStateReadings.rhandPose = Controller.getPoseValue(Controller.Standard.RightHand); - currentStateReadings.lhandPose = Controller.getPoseValue(Controller.Standard.LeftHand); - - // back length - var headMinusHipLean = Vec3.subtract(currentStateReadings.headPose.translation, DEFAULT_HIPS_POSITION); - currentStateReadings.backLength = Vec3.length(headMinusHipLean); - // print("back length and default " + currentStateReadings.backLength + " " + DEFAULT_TORSO_LENGTH); - - // mode height - addToModeArray(modeArray, currentStateReadings.headPose.translation.y); - modeHeight = findMode(modeArray, modeHeight, currentStateReadings.backLength, DEFAULT_TORSO_LENGTH, - currentStateReadings.headPose.translation.y); - currentStateReadings.diffFromMode = modeHeight - currentStateReadings.headPose.translation.y; - - // hand direction - var leftHandLateralPoseVelocity = currentStateReadings.lhandPose.velocity; - leftHandLateralPoseVelocity.y = 0.0; - var rightHandLateralPoseVelocity = currentStateReadings.rhandPose.velocity; - rightHandLateralPoseVelocity.y = 0.0; - var headLateralPoseVelocity = currentStateReadings.headPose.velocity; - headLateralPoseVelocity.y = 0.0; - handDotHead[LEFT] = Vec3.dot(Vec3.normalize(leftHandLateralPoseVelocity), Vec3.normalize(headLateralPoseVelocity)); - handDotHead[RIGHT] = Vec3.dot(Vec3.normalize(rightHandLateralPoseVelocity), Vec3.normalize(headLateralPoseVelocity)); - - // average head position - headAveragePosition = Vec3.mix(headAveragePosition, currentStateReadings.headPose.translation, AVERAGING_RATE); - currentStateReadings.diffFromAveragePosition = Vec3.subtract(currentStateReadings.headPose.translation, - headAveragePosition); - - // average height - averageHeight = currentStateReadings.headPose.translation.y * HEIGHT_AVERAGING_RATE + - averageHeight * (1.0 - HEIGHT_AVERAGING_RATE); - currentStateReadings.diffFromAverageHeight = Math.abs(currentStateReadings.headPose.translation.y - averageHeight); - - // eulers diff - headEulers = Quat.safeEulerAngles(currentStateReadings.headPose.rotation); - headAverageOrientation = Quat.slerp(headAverageOrientation, currentStateReadings.headPose.rotation, AVERAGING_RATE); - headAverageEulers = Quat.safeEulerAngles(headAverageOrientation); - currentStateReadings.diffFromAverageEulers = Vec3.subtract(headAverageEulers, headEulers); - - // headpose rig space is for determining when to recenter rotation. - var headPoseRigSpace = Quat.multiply(CHANGE_OF_BASIS_ROTATION, currentStateReadings.headPose.rotation); - headPoseAverageOrientation = Quat.slerp(headPoseAverageOrientation, headPoseRigSpace, AVERAGING_RATE); - var headPoseAverageEulers = Quat.safeEulerAngles(headPoseAverageOrientation); - - // make the signal colors reflect the current thresholds that have been crossed - updateSignalColors(); - - SPINE_STRETCH_LIMIT = (0.04) * DEFAULT_TORSO_LENGTH * MyAvatar.scale; - - //print("the spine stretch limit is " + SPINE_STRETCH_LIMIT + " head avatar space is " + currentStateReadings.headPose.translation.y); - //print("the current back length is " + currentStateReadings.backLength + " " + DEFAULT_TORSO_LENGTH); - // Conditions for taking a step. - // 1. off the base of support. front, lateral, back edges. - // 2. head is not lower than the height mode value by more than the maxHeightChange tolerance - // 3. the angular velocity of the head is not greater than the threshold value - // ie this reflects the speed the head is rotating away from having up = (0,1,0) in Avatar frame.. - // 4. the hands velocity vector has the same direction as the head, within the given tolerance - // the tolerance is an acos value, -1 means the hands going in any direction will not block translating - // up to 1 where the hands velocity direction must exactly match that of the head. -1 threshold disables this condition. - // 5. the angular velocity xz magnitude for each hand is below the threshold value - // ie here this reflects the speed that each hand is rotating away from having up = (0,1,0) in Avatar frame. - // 6. head velocity is below step threshold - // 7. head has moved further than the threshold from the running average position of the head. - // 8. head height is not lower than the running average head height with a difference of maxHeightChange. - // 9. head's rotation in avatar space is not pitching or rolling greater than the pitch or roll thresholds - if (!withinBaseOfSupport(currentStateReadings.headPose.translation) && - withinThresholdOfStandingHeightMode(currentStateReadings.diffFromMode) && - headAngularVelocityBelowThreshold(currentStateReadings.headPose.angularVelocity) && - handDirectionMatchesHeadDirection(currentStateReadings.lhandPose, currentStateReadings.rhandPose) && - handAngularVelocityBelowThreshold(currentStateReadings.lhandPose, currentStateReadings.rhandPose) && - headVelocityGreaterThanThreshold(Vec3.length(currentStateReadings.headPose.velocity)) && - headMovedAwayFromAveragePosition(currentStateReadings.diffFromAveragePosition) && - headLowerThanHeightAverage(currentStateReadings.diffFromAverageHeight) && - isHeadLevel(currentStateReadings.diffFromAverageEulers)) { - - if (stepTimer < 0.0) { //!MyAvatar.isRecenteringHorizontally() - print("trigger recenter========================================================"); - MyAvatar.triggerHorizontalRecenter(); - stepTimer = STEP_TIME_SECS; - } - } else if ((currentStateReadings.backLength > (DEFAULT_TORSO_LENGTH + SPINE_STRETCH_LIMIT)) && - (failsafeSignalTimer < 0.0) && HMD.active) { - // do the failsafe recenter. - // failsafeFlag stops repeated setting of failsafe button color. - // RESET_MODE false forces a reset of the height - RESET_MODE = false; - failsafeFlag = true; - failsafeSignalTimer = FAILSAFE_TIMEOUT; - MyAvatar.triggerHorizontalRecenter(); - tablet.emitScriptEvent(JSON.stringify({ "type": "failsafe", "id": "failsafeSignal", "data": { "value": "green" } })); - // in fail safe we debug print the values that were blocking us. - print("failsafe debug---------------------------------------------------------------"); - propArray.forEach(function (prop) { - print(prop.name); - if (!prop.signalOn) { - print(prop.signalType + " contributed to failsafe call"); - } - }); - print("end failsafe debug---------------------------------------------------------------"); - - } - - if ((failsafeSignalTimer < 0.0) && failsafeFlag) { - failsafeFlag = false; - tablet.emitScriptEvent(JSON.stringify({ "type": "failsafe", "id": "failsafeSignal", "data": { "value": "orange" } })); - } - - stepTimer -= dt; - failsafeSignalTimer -= dt; - - if (!HMD.active) { - RESET_MODE = false; - } - - if (Math.abs(headPoseAverageEulers.y) > HEAD_TURN_THRESHOLD) { - // Turn feet - // MyAvatar.triggerRotationRecenter(); - // headPoseAverageOrientation = { x: 0, y: 0, z: 0, w: 1 }; - } -} - -function shutdownTabletApp() { - // GlobalDebugger.stop(); - tablet.removeButton(tabletButton); - if (activated) { - tablet.webEventReceived.disconnect(onWebEventReceived); - tablet.gotoHomeScreen(); - } - tablet.screenChanged.disconnect(onScreenChanged); -} - -tabletButton.clicked.connect(manageClick); -tablet.screenChanged.connect(onScreenChanged); - -Script.setTimeout(function() { - DEFAULT_HIPS_POSITION = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(MyAvatar.getJointIndex("Hips")); - DEFAULT_HEAD_POSITION = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(MyAvatar.getJointIndex("Head")); - DEFAULT_TORSO_LENGTH = Vec3.length(Vec3.subtract(DEFAULT_HEAD_POSITION, DEFAULT_HIPS_POSITION)); - SPINE_STRETCH_LIMIT = (0.04) * DEFAULT_TORSO_LENGTH * MyAvatar.scale; -},(4*LOADING_DELAY)); - -Script.update.connect(update); -Controller.keyPressEvent.connect(onKeyPress); -Script.scriptEnding.connect(function () { - MyAvatar.hmdLeanRecenterEnabled = true; - Script.update.disconnect(update); - shutdownTabletApp(); -}); From 2d2a4804f7790f41a8b9af373c5274ff73e7c84c Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 6 Sep 2018 17:49:52 -0300 Subject: [PATCH 323/744] Add android settings screen. Add AEC setting --- android/app/src/main/AndroidManifest.xml | 10 +++ android/app/src/main/cpp/native.cpp | 46 ++++++++++++++ .../hifiinterface/InterfaceActivity.java | 7 +++ .../hifiinterface/MainActivity.java | 23 +++++++ .../fragment/SettingsFragment.java | 63 +++++++++++++++++++ .../receiver/HeadsetStateReceiver.java | 20 ++++++ .../app/src/main/res/menu/menu_navigation.xml | 5 ++ android/app/src/main/res/values/strings.xml | 5 ++ android/app/src/main/res/xml/settings.xml | 11 ++++ interface/src/AndroidHelper.cpp | 17 +++++ interface/src/AndroidHelper.h | 1 + libraries/audio-client/src/AudioClient.cpp | 6 +- libraries/audio-client/src/AudioClient.h | 8 +++ 13 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java create mode 100644 android/app/src/main/res/xml/settings.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7255e1f295..b216819ed0 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + @@ -75,6 +76,15 @@ android:enabled="true" android:exported="false" android:process=":breakpad_uploader"/> + + + + + + diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index ce5af01f29..6b44b2dc7a 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -355,5 +355,51 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_WebViewActivity_nativeProcessU AndroidHelper::instance().processURL(QString::fromUtf8(nativeString)); } +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_fragment_SettingsFragment_updateHifiSetting(JNIEnv *env, + jobject instance, + jstring group_, + jstring key_, + jboolean value_) { + const char *c_group = env->GetStringUTFChars(group_, 0); + const char *c_key = env->GetStringUTFChars(key_, 0); + + const QString group = QString::fromUtf8(c_group); + const QString key = QString::fromUtf8(c_key); + + env->ReleaseStringUTFChars(group_, c_group); + env->ReleaseStringUTFChars(key_, c_key); + + bool value = value_; + + Setting::Handle setting { QStringList() << group << key , !value }; + setting.set(value); +} + +JNIEXPORT jboolean JNICALL +Java_io_highfidelity_hifiinterface_fragment_SettingsFragment_getHifiSettingBoolean(JNIEnv *env, + jobject instance, + jstring group_, + jstring key_, + jboolean defaultValue) { + const char *c_group = env->GetStringUTFChars(group_, 0); + const char *c_key = env->GetStringUTFChars(key_, 0); + + const QString group = QString::fromUtf8(c_group); + const QString key = QString::fromUtf8(c_key); + + env->ReleaseStringUTFChars(group_, c_group); + env->ReleaseStringUTFChars(key_, c_key); + + Setting::Handle setting { QStringList() << group << key , defaultValue}; + return setting.get(); +} + +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_receiver_HeadsetStateReceiver_notifyHeadsetOn(JNIEnv *env, + jobject instance, + jboolean pluggedIn) { + AndroidHelper::instance().notifyHeadsetOn(pluggedIn); +} } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index f161783d6a..08e66a2f42 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -13,6 +13,7 @@ package io.highfidelity.hifiinterface; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -40,6 +41,7 @@ import java.util.HashMap; import java.util.List; import io.highfidelity.hifiinterface.fragment.WebViewFragment; +import io.highfidelity.hifiinterface.receiver.HeadsetStateReceiver; /*import com.google.vr.cardboard.DisplaySynchronizer; import com.google.vr.cardboard.DisplayUtils; @@ -55,6 +57,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW private static final int NORMAL_DPI = 160; private Vibrator mVibrator; + private HeadsetStateReceiver headsetStateReceiver; //public static native void handleHifiURL(String hifiURLString); private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager); @@ -151,6 +154,8 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW layoutParams.resolveLayoutDirection(View.LAYOUT_DIRECTION_RTL); qtLayout.addView(webSlidingDrawer, layoutParams); webSlidingDrawer.setVisibility(View.GONE); + + headsetStateReceiver = new HeadsetStateReceiver(); } @Override @@ -161,6 +166,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW } else { nativeEnterBackground(); } + unregisterReceiver(headsetStateReceiver); //gvrApi.pauseTracking(); } @@ -183,6 +189,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW nativeEnterForeground(); surfacesWorkaround(); keepInterfaceRunning = false; + registerReceiver(headsetStateReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); //gvrApi.resumeTracking(); } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index db6f0fca24..4c6d05a3e8 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -33,6 +33,7 @@ import io.highfidelity.hifiinterface.fragment.FriendsFragment; import io.highfidelity.hifiinterface.fragment.HomeFragment; import io.highfidelity.hifiinterface.fragment.LoginFragment; import io.highfidelity.hifiinterface.fragment.PolicyFragment; +import io.highfidelity.hifiinterface.fragment.SettingsFragment; import io.highfidelity.hifiinterface.task.DownloadProfileImageTask; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, @@ -80,6 +81,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people); + updateDebugMenu(mNavigationView.getMenu()); + Toolbar toolbar = findViewById(R.id.toolbar); toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle); setSupportActionBar(toolbar); @@ -108,6 +111,16 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On } } + private void updateDebugMenu(Menu menu) { + if (BuildConfig.DEBUG) { + for (int i=0; i < menu.size(); i++) { + if (menu.getItem(i).getItemId() == R.id.action_debug_settings) { + menu.getItem(i).setVisible(true); + } + } + } + } + private void loadFragment(String fragment) { switch (fragment) { case "Login": @@ -151,6 +164,13 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true); } + private void loadSettingsFragment() { + SettingsFragment fragment = SettingsFragment.newInstance(); + + loadFragment(fragment, getString(R.string.settings), getString(R.string.tagSettings), true); + } + + private void loadFragment(Fragment fragment, String title, String tag, boolean addToBackStack) { FragmentManager fragmentManager = getFragmentManager(); @@ -241,6 +261,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On case R.id.action_people: loadPeopleFragment(); return true; + case R.id.action_debug_settings: + loadSettingsFragment(); + return true; } return false; } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java new file mode 100644 index 0000000000..cc23665e72 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java @@ -0,0 +1,63 @@ +package io.highfidelity.hifiinterface.fragment; + +import android.content.SharedPreferences; +import android.media.audiofx.AcousticEchoCanceler; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.support.annotation.Nullable; + +import io.highfidelity.hifiinterface.R; + +public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { + + public native void updateHifiSetting(String group, String key, boolean value); + public native boolean getHifiSettingBoolean(String group, String key, boolean defaultValue); + + private final String HIFI_SETTINGS_ANDROID_GROUP = "Android"; + private final String HIFI_SETTINGS_AEC_KEY = "aec"; + private final String PREFERENCE_KEY_AEC = "aec"; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings); + + if (!AcousticEchoCanceler.isAvailable()) { + getPreferenceScreen().getPreferenceManager().findPreference("aec").setEnabled(false); + } + + getPreferenceScreen().getSharedPreferences().edit().putBoolean(PREFERENCE_KEY_AEC, + getHifiSettingBoolean(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, false)); + } + + public static SettingsFragment newInstance() { + SettingsFragment fragment = new SettingsFragment(); + return fragment; + } + + @Override + public void onResume() { + super.onResume(); + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onPause() { + super.onPause(); + getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + Preference pref = findPreference(key); + switch (key) { + case "aec": + updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, sharedPreferences.getBoolean(key, false)); + break; + default: + break; + } + } +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java new file mode 100644 index 0000000000..29bc1c49f2 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java @@ -0,0 +1,20 @@ +package io.highfidelity.hifiinterface.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.util.Log; + +public class HeadsetStateReceiver extends BroadcastReceiver { + + private native void notifyHeadsetOn(boolean pluggedIn); + + @Override + public void onReceive(Context context, Intent intent) { + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + + Log.d("[HEADSET] " , "BR - Wired headset on:" + audioManager.isWiredHeadsetOn()); + notifyHeadsetOn(audioManager.isWiredHeadsetOn()); + } +} diff --git a/android/app/src/main/res/menu/menu_navigation.xml b/android/app/src/main/res/menu/menu_navigation.xml index 3cce64f9f5..142af5d146 100644 --- a/android/app/src/main/res/menu/menu_navigation.xml +++ b/android/app/src/main/res/menu/menu_navigation.xml @@ -9,4 +9,9 @@ android:id="@+id/action_people" android:title="@string/people" /> + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index b158aba59d..abde15f484 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -29,4 +29,9 @@ tagFragmentLogin tagFragmentPolicy tagFragmentPeople + tagSettings + Settings + AEC + Acoustic Echo Cancellation + Developer diff --git a/android/app/src/main/res/xml/settings.xml b/android/app/src/main/res/xml/settings.xml new file mode 100644 index 0000000000..5ec47b1aff --- /dev/null +++ b/android/app/src/main/res/xml/settings.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp index 419382f2cb..35bf094591 100644 --- a/interface/src/AndroidHelper.cpp +++ b/interface/src/AndroidHelper.cpp @@ -10,6 +10,7 @@ // #include "AndroidHelper.h" #include +#include #include "Application.h" #if defined(qApp) @@ -18,6 +19,7 @@ #define qApp (static_cast(QCoreApplication::instance())) AndroidHelper::AndroidHelper() { + qRegisterMetaType("QAudio::Mode"); } AndroidHelper::~AndroidHelper() { @@ -56,3 +58,18 @@ void AndroidHelper::processURL(const QString &url) { qApp->acceptURL(url); } } + +void AndroidHelper::notifyHeadsetOn(bool pluggedIn) { +#if defined (Q_OS_ANDROID) + auto audioClient = DependencyManager::get(); + if (audioClient) { + QAudioDeviceInfo activeDev = audioClient->getActiveAudioDevice(QAudio::AudioInput); + Setting::Handle enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false); + if ((pluggedIn || !enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_RECOGNITION) { + QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_RECOGNITION)); + } else if ( (!pluggedIn && enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_COMMUNICATION) { + QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_COMMUNICATION)); + } + } +#endif +} diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h index 03d92f91d9..11b84e4025 100644 --- a/interface/src/AndroidHelper.h +++ b/interface/src/AndroidHelper.h @@ -29,6 +29,7 @@ public: void performHapticFeedback(int duration); void processURL(const QString &url); + void notifyHeadsetOn(bool pluggedIn); AndroidHelper(AndroidHelper const&) = delete; void operator=(AndroidHelper const&) = delete; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 6a9363f309..e23b8ac3cd 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -53,7 +53,6 @@ #include "AudioHelpers.h" #if defined(Q_OS_ANDROID) -#define VOICE_RECOGNITION "voicerecognition" #include #endif @@ -451,9 +450,12 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #if defined (Q_OS_ANDROID) if (mode == QAudio::AudioInput) { + Setting::Handle enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false); + bool aecEnabled = enableAEC.get(); auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); for (auto inputDevice : inputDevices) { - if (inputDevice.deviceName() == VOICE_RECOGNITION) { + if ((aecEnabled && inputDevice.deviceName() == VOICE_COMMUNICATION) || + (!aecEnabled && inputDevice.deviceName() == VOICE_RECOGNITION)) { return inputDevice; } } diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 8599c990a3..fa7ac40a16 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -64,6 +64,14 @@ #pragma warning( pop ) #endif +#if defined (Q_OS_ANDROID) +#define VOICE_RECOGNITION "voicerecognition" +#define VOICE_COMMUNICATION "voicecommunication" + +#define ANDROID_SETTINGS_GROUP "Android" +#define SETTING_AEC_KEY "aec" +#endif + class QAudioInput; class QAudioOutput; class QIODevice; From 38bfee7b723704a3a92d988b389b923b87c173ef Mon Sep 17 00:00:00 2001 From: sam gateau Date: Thu, 6 Sep 2018 13:54:21 -0700 Subject: [PATCH 324/744] Fix compilation on android and add the simple getBufferIDUnsynced in bindUniformBuffer --- .../gpu-gl-common/src/gpu/gl/GLBackend.h | 2 +- .../src/gpu/gl/GLBackendInput.cpp | 22 +++++-------------- .../src/gpu/gl/GLBackendPipeline.cpp | 13 +++++------ libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h | 6 ++--- libraries/gpu-gl/src/gpu/gl41/GL41Backend.h | 2 +- .../gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp | 4 ++-- libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 2 +- .../gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp | 5 ++--- libraries/gpu-gles/src/gpu/gles/GLESBackend.h | 1 + .../src/gpu/gles/GLESBackendBuffer.cpp | 4 ++++ 10 files changed, 25 insertions(+), 36 deletions(-) diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 2fa2df5bfa..9b3a28e6fd 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -239,7 +239,7 @@ public: virtual GLuint getFramebufferID(const FramebufferPointer& framebuffer) = 0; virtual GLuint getTextureID(const TexturePointer& texture) final; virtual GLuint getBufferID(const Buffer& buffer) = 0; - virtual GLuint getBufferIDUnsafe(const Buffer& buffer) = 0; + virtual GLuint getBufferIDUnsynced(const Buffer& buffer) = 0; virtual GLuint getQueryID(const QueryPointer& query) = 0; virtual GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) = 0; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp index 9fe1aa4029..f326ef39b9 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp @@ -40,23 +40,11 @@ void GLBackend::do_setInputBuffer(const Batch& batch, size_t paramOffset) { BufferPointer buffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); uint32 channel = batch._params[paramOffset + 3]._uint; - // if (channel < getNumInputBuffers()) { + if (channel < getNumInputBuffers()) { bool isModified = false; if (_input._buffers[channel] != buffer) { _input._buffers[channel] = buffer; - - GLuint vbo = 0; - if (buffer) { - // vbo = getBufferID((*buffer)); - // vbo = getBufferIDUnsafe((*buffer)); - auto* object = Backend::getGPUObject((*buffer)); - - if (object) { - vbo = object->_buffer; - } - } - _input._bufferVBOs[channel] = vbo; - + _input._bufferVBOs[channel] = getBufferIDUnsynced((*buffer)); isModified = true; } @@ -73,7 +61,7 @@ void GLBackend::do_setInputBuffer(const Batch& batch, size_t paramOffset) { if (isModified) { _input._invalidBuffers.set(channel); } - // } + } } void GLBackend::initInput() { @@ -135,7 +123,7 @@ void GLBackend::do_setIndexBuffer(const Batch& batch, size_t paramOffset) { if (indexBuffer != _input._indexBuffer) { _input._indexBuffer = indexBuffer; if (indexBuffer) { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getBufferIDUnsafe(*indexBuffer)); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getBufferIDUnsynced(*indexBuffer)); } else { // FIXME do we really need this? Is there ever a draw call where we care that the element buffer is null? glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -152,7 +140,7 @@ void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) { if (buffer != _input._indirectBuffer) { _input._indirectBuffer = buffer; if (buffer) { - glBindBuffer(GL_DRAW_INDIRECT_BUFFER, getBufferIDUnsafe(*buffer)); + glBindBuffer(GL_DRAW_INDIRECT_BUFFER, getBufferIDUnsynced(*buffer)); } else { // FIXME do we really need this? Is there ever a draw call where we care that the element buffer is null? glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index fd67202863..05ded3eece 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -85,6 +85,8 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { auto& cameraCorrectionBuffer = _transform._viewCorrectionEnabled ? _pipeline._cameraCorrectionBuffer._buffer : _pipeline._cameraCorrectionBufferIdentity._buffer; + // Because we don't sync Buffers in the bindUniformBuffer, let s force this buffer synced + getBufferID(*cameraCorrectionBuffer); bindUniformBuffer(gpu::slot::buffer::CameraCorrection, cameraCorrectionBuffer, 0, sizeof(CameraCorrection)); } (void)CHECK_GL_ERROR(); @@ -170,13 +172,10 @@ void GLBackend::bindUniformBuffer(uint32_t slot, const BufferPointer& buffer, GL return; } - // Sync BufferObject - auto* object = syncGPUObject(*bufferState.buffer); - // auto glBO = getBufferIDUnsafe(*buffer); - - if (object) { - glBindBufferRange(GL_UNIFORM_BUFFER, slot, object->_buffer, bufferState.offset, bufferState.size); - + // Grab the true gl Buffer object + auto glBO = getBufferIDUnsynced(*buffer); + if (glBO) { + glBindBufferRange(GL_UNIFORM_BUFFER, slot, glBO, bufferState.offset, bufferState.size); _uniform._buffers[slot] = bufferState; (void)CHECK_GL_ERROR(); } else { diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h b/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h index 8efbe03b90..b9fe125c8d 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h @@ -50,13 +50,11 @@ public: } template - static GLuint getIdUnsafe(GLBackend& backend, const Buffer& buffer) { + static GLuint getIdUnsynced(GLBackend& backend, const Buffer& buffer) { GLBufferType* object = Backend::getGPUObject(buffer); - if (object) { return object->_buffer; - } - else { + } else { return 0; } } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index a09b3e9297..f4078f5479 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -134,7 +134,7 @@ protected: GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; - GLuint getBufferIDUnsafe(const Buffer& buffer) override; + GLuint getBufferIDUnsynced(const Buffer& buffer) override; GLuint getResourceBufferID(const Buffer& buffer); GLBuffer* syncGPUObject(const Buffer& buffer) override; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp index 97ac2739eb..ac5d5ee0c9 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp @@ -83,8 +83,8 @@ GLuint GL41Backend::getBufferID(const Buffer& buffer) { return GL41Buffer::getId(*this, buffer); } -GLuint GL41Backend::getBufferIDUnsafe(const Buffer& buffer) { - return GL41Backend::getBufferID(buffer); +GLuint GL41Backend::getBufferIDUnsynced(const Buffer& buffer) { + return GL41Buffer::getIdUnsynced(*this, buffer); } GLuint GL41Backend::getResourceBufferID(const Buffer& buffer) { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 5da4506773..a100faf432 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -235,7 +235,7 @@ protected: GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; - GLuint getBufferIDUnsafe(const Buffer& buffer) override; + GLuint getBufferIDUnsynced(const Buffer& buffer) override; GLBuffer* syncGPUObject(const Buffer& buffer) override; GLTexture* syncGPUObject(const TexturePointer& texture) override; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp index 0d26f8f412..6d17923ebd 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp @@ -51,9 +51,8 @@ GLuint GL45Backend::getBufferID(const Buffer& buffer) { return GL45Buffer::getId(*this, buffer); } -GLuint GL45Backend::getBufferIDUnsafe(const Buffer& buffer) { - //return GL45Buffer::getId(*this, buffer); - return GL45Buffer::getIdUnsafe(*this, buffer); +GLuint GL45Backend::getBufferIDUnsynced(const Buffer& buffer) { + return GL45Buffer::getIdUnsynced(*this, buffer); } GLBuffer* GL45Backend::syncGPUObject(const Buffer& buffer) { diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h index 785f4c3ef9..c757de0a72 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h @@ -130,6 +130,7 @@ protected: GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; + GLuint getBufferIDUnsynced(const Buffer& buffer) override; GLuint getResourceBufferID(const Buffer& buffer); GLBuffer* syncGPUObject(const Buffer& buffer) override; diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp index 17fdad8377..7dd08df409 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp @@ -64,6 +64,10 @@ GLuint GLESBackend::getBufferID(const Buffer& buffer) { return GLESBuffer::getId(*this, buffer); } +GLuint GLESBackend::getBufferIDUnsynced(const Buffer& buffer) { + return GLESBuffer::getIdUnsynced(*this, buffer); +} + GLBuffer* GLESBackend::syncGPUObject(const Buffer& buffer) { return GLESBuffer::sync(*this, buffer); } From 6544e9f0b004cb9c6b43da5405e2f2f1c98fbd46 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Thu, 6 Sep 2018 13:58:03 -0700 Subject: [PATCH 325/744] Removing the commented debug code --- libraries/fbx/src/FBXReader_Mesh.cpp | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index 7b83e74ebc..19bd99ec5d 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -781,20 +781,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { attribChannel = 2; totalAttribBufferSize = totalVertsSize - positionsSize - normalsAndTangentsSize; - } /*else { - auto posBuffer = std::make_shared(); - posBuffer->setData(positionsSize, (const gpu::Byte*) vertBuffer->getData() + positionsOffset); - vertexBufferStream->addBuffer(posBuffer, 0, positionElement.getSize()); - - // update channels and attribBuffer size accordingly - interleavePositions = false; - interleaveNormalsTangents = true; - - tangentChannel = 1; - attribChannel = 1; - - totalAttribBufferSize = totalVertsSize - positionsSize; - }*/ + } // Define the vertex format, compute the offset for each attributes as we append them to the vertex format gpu::Offset bufOffset = 0; From 00e10ad684f5823e571c16e9e5e4f536877341ff Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Thu, 6 Sep 2018 14:05:51 -0700 Subject: [PATCH 326/744] Updating sign up text, moving forgotten info links --- .../resources/qml/LoginDialog/LinkAccountBody.qml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 423f6cc73b..783027761a 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -128,9 +128,8 @@ Item { ShortcutText { z: 10 anchors { - left: usernameField.right - top: usernameField.bottom - leftMargin: usernameField.textFieldLabel.contentWidth + 10 + right: usernameField.right + top: usernameField.top topMargin: -19 } @@ -157,9 +156,8 @@ Item { ShortcutText { z: 10 anchors { - left: passwordField.right - top: passwordField.bottom - leftMargin: passwordField.textFieldLabel.contentWidth + 10 + right: passwordField.right + top: passwordField.top topMargin: -19 } @@ -232,7 +230,7 @@ Item { RalewaySemiBold { size: hifi.fontSizes.inputLabel anchors.verticalCenter: parent.verticalCenter - text: qsTr("Don't have an account?") + text: qsTr("New to High Fidelity?") } Button { From aabfda6eb5a90072e7c2f84c8e032ed4f4322f9c Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Thu, 6 Sep 2018 14:16:42 -0700 Subject: [PATCH 327/744] Adding descriptive text --- .../qml/LoginDialog/LinkAccountBody.qml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 783027761a..60c7a26e89 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -87,6 +87,22 @@ Item { height: 48 } + ShortcutText { + id: flavorText + anchors { + top: parent.top + left: parent.left + margins: 0 + topMargin: hifi.dimensions.contentSpacing.y + } + + text: qsTr("Sign in to High Fidelity to make friends, get HFC, and buy interesting things on the Marketplace!") + wrapMode: Text.WordWrap + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + horizontalAlignment: Text.AlignHCenter + } + ShortcutText { id: mainTextContainer anchors { From 98dafa3b67ca6e87432076c4b9c43778b31741fc Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 6 Sep 2018 14:31:49 -0700 Subject: [PATCH 328/744] fix overlayParentID --- scripts/system/interstitialPage.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index c56e16d429..c95845656c 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -419,6 +419,10 @@ }); MyAvatar.sensorToWorldScaleChanged.connect(scaleInterstitialPage); + MyAvatar.sessionUUIDChanged.connect(function() { + var avatarSessionUUID = MyAvatar.sessionUUID; + Overlays.editOverlay(loadingSphereID, { parentID: avatarSessionUUID }); + }); var toggle = true; if (DEBUG) { From 021146e4f0d6911d9c9ca57e88719932730c002a Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 6 Sep 2018 15:33:52 -0700 Subject: [PATCH 329/744] minor tweak to default avatar-update sort coefficients --- libraries/avatars/src/AvatarData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 6896883f74..13adc803dd 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2822,7 +2822,7 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f; -float AvatarData::_avatarSortCoefficientSize { 4.0f }; +float AvatarData::_avatarSortCoefficientSize { 8.0f }; float AvatarData::_avatarSortCoefficientCenter { 4.0f }; float AvatarData::_avatarSortCoefficientAge { 1.0f }; From 229e9624e62f2afe29b55cccef4fdf2fc4d7eb7c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 6 Sep 2018 15:34:28 -0700 Subject: [PATCH 330/744] remove bad bounding box calculations in avatar-mixer --- .../src/avatars/AvatarMixerClientData.h | 1 + .../src/avatars/AvatarMixerSlave.cpp | 43 +++++++------------ 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index ee27f9bb0f..81a4a58769 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -45,6 +45,7 @@ public: int parseData(ReceivedMessage& message) override; AvatarData& getAvatar() { return *_avatar; } + const AvatarData& getAvatar() const { return *_avatar; } const AvatarData* getConstAvatarData() const { return _avatar.get(); } AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index a61f65ffb0..3e83a78341 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -212,6 +212,15 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { _stats.jobElapsedTime += (end - start); } +AABox computeBubbleBox(const AvatarData& avatar, float bubbleExpansionFactor) { + glm::vec3 position = avatar.getClientGlobalPosition(); + glm::vec3 scale = 2.0f * (avatar.getClientGlobalPosition()- avatar.getGlobalBoundingBoxCorner()); + scale *= bubbleExpansionFactor; + const glm::vec3 MIN_BUBBLE_SCALE(0.3f, 1.3f, 0.3); + scale = glm::max(scale, MIN_BUBBLE_SCALE); + return AABox(position - 0.5f * scale, scale); +} + void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { const Node* destinationNode = node.data(); @@ -275,18 +284,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // setup a PacketList for the avatarPackets auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); - // Define the minimum bubble size - const glm::vec3 minBubbleSize = avatar.getSensorToWorldScale() * glm::vec3(0.3f, 1.3f, 0.3f); - // Define the scale of the box for the current node - glm::vec3 nodeBoxScale = (avatar.getClientGlobalPosition() - avatar.getGlobalBoundingBoxCorner()) * 2.0f * avatar.getSensorToWorldScale(); - // Set up the bounding box for the current node - AABox nodeBox(avatar.getGlobalBoundingBoxCorner(), nodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) { - nodeBox.setScaleStayCentered(minBubbleSize); - } - // Quadruple the scale of first bounding box - nodeBox.embiggen(4.0f); + // compute node bounding box + const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically + AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR ); class SortableAvatar: public PrioritySortUtil::Sortable { public: @@ -295,7 +295,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) : _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {} glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); } float getRadius() const override { - glm::vec3 nodeBoxHalfScale = (_avatar->getClientGlobalPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale()); + glm::vec3 nodeBoxHalfScale = _avatar->getClientGlobalPosition() - _avatar->getGlobalBoundingBoxCorner(); return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); } uint64_t getTimestamp() const override { @@ -351,20 +351,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // Check to see if the space bubble is enabled // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored if (destinationNode->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { - float sensorToWorldScale = avatarClientNodeData->getAvatarSharedPointer()->getSensorToWorldScale(); - // Define the scale of the box for the current other node - glm::vec3 otherNodeBoxScale = (avatarClientNodeData->getPosition() - avatarClientNodeData->getGlobalBoundingBoxCorner()) * 2.0f * sensorToWorldScale; - // Set up the bounding box for the current other node - AABox otherNodeBox(avatarClientNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { - otherNodeBox.setScaleStayCentered(minBubbleSize); - } - // Change the scale of other bounding box - // (This is an arbitrary number determined empirically) - otherNodeBox.embiggen(2.4f); - // Perform the collision check between the two bounding boxes + const float OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR = 2.4f; // magic number determined empirically + AABox otherNodeBox = computeBubbleBox(avatarClientNodeData->getAvatar(), OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR); if (nodeBox.touches(otherNodeBox)) { nodeData->ignoreOther(destinationNode, avatarNode); shouldIgnore = !getsAnyIgnored; @@ -445,7 +434,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // determine if avatar is in view which determines how much data to send glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition(); - glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f * otherAvatar->getSensorToWorldScale(); + glm::vec3 otherNodeBoxScale = otherPosition - otherNodeData->getGlobalBoundingBoxCorner(); AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); bool isInView = nodeData->otherAvatarInView(otherNodeBox); From 0e785f9bdb0746888209a8826bd48a1fb6915198 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 6 Sep 2018 15:35:26 -0700 Subject: [PATCH 331/744] Fix MS17877: Persist Connections table sort choice --- interface/resources/qml/hifi/Pal.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index cbab83ea28..35a0078d32 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -780,6 +780,12 @@ Rectangle { headerVisible: true; sortIndicatorColumn: settings.connectionsSortIndicatorColumn; sortIndicatorOrder: settings.connectionsSortIndicatorOrder; + onSortIndicatorColumnChanged: { + settings.connectionsSortIndicatorColumn = sortIndicatorColumn; + } + onSortIndicatorOrderChanged: { + settings.connectionsSortIndicatorOrder = sortIndicatorOrder; + } TableViewColumn { id: connectionsUserNameHeader; From 734956211151b4ee4566732e743f5b86bc87dea1 Mon Sep 17 00:00:00 2001 From: sam gateau Date: Thu, 6 Sep 2018 15:50:31 -0700 Subject: [PATCH 332/744] Removing commented code --- libraries/gpu/src/gpu/Batch.h | 5 +---- libraries/graphics/src/graphics/Geometry.cpp | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 2bead507b8..bb38379a66 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -417,10 +417,7 @@ public: } const Data& get(uint32 offset) const { - /* if (offset >= _items.size()) { - static const Data EMPTY; - return EMPTY; - }*/ + assert((offset >= _items.size())); return (_items.data() + offset)->_data; } diff --git a/libraries/graphics/src/graphics/Geometry.cpp b/libraries/graphics/src/graphics/Geometry.cpp index 5a374ae8d0..a983ba07b4 100755 --- a/libraries/graphics/src/graphics/Geometry.cpp +++ b/libraries/graphics/src/graphics/Geometry.cpp @@ -119,7 +119,6 @@ Box Mesh::evalPartBound(int partNum) const { for (;index != endIndex; index++) { // skip primitive restart indices if ((*index) != PRIMITIVE_RESTART_INDEX) { - // box += vertices[(*index)]; box += _vertexBuffer.get(part._baseVertex + (*index)); } } @@ -140,7 +139,6 @@ Box Mesh::evalPartsBound(int partStart, int partEnd) const { for (;index != endIndex; index++) { // skip primitive restart indices if ((*index) != (uint) PRIMITIVE_RESTART_INDEX) { - //partBound += vertices[(*index)]; partBound += _vertexBuffer.get((*part)._baseVertex + (*index)); } } From a908c1554f4d1a35d88577cfaf47b09d2948645f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 6 Sep 2018 16:04:54 -0700 Subject: [PATCH 333/744] make sure ignoredNode is available before sending packet --- assignment-client/src/avatars/AvatarMixer.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 0289a8aa3f..561afee296 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -655,11 +655,14 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer if (addToIgnore) { senderNode->addIgnoredNode(ignoredUUID); - // send a reliable kill packet to remove the sending avatar for the ignored avatar - auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); - killPacket->write(senderNode->getUUID().toRfc4122()); - killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected); - nodeList->sendPacket(std::move(killPacket), *ignoredNode); + if (ignoredNode) { + // send a reliable kill packet to remove the sending avatar for the ignored avatar + auto killPacket = NLPacket::create(PacketType::KillAvatar, + NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); + killPacket->write(senderNode->getUUID().toRfc4122()); + killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected); + nodeList->sendPacket(std::move(killPacket), *ignoredNode); + } } else { senderNode->removeIgnoredNode(ignoredUUID); } From 3d75d4726af0ce63318d1a40ac500374c1260c5d Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 6 Sep 2018 16:04:59 -0700 Subject: [PATCH 334/744] scale invertly once passing minimum --- .../system/libraries/entitySelectionTool.js | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 945f6a88d7..5b82b7d38b 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -2283,39 +2283,25 @@ SelectionDisplay = (function() { var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector)); + var newDimensions; if (proportional) { var viewportDimensions = Controller.getViewportDimensions(); var mouseXDifference = (event.x - beginMouseEvent.x) / viewportDimensions.x; var mouseYDifference = (beginMouseEvent.y - event.y) / viewportDimensions.y; var mouseDifference = mouseXDifference + mouseYDifference; - changeInDimensions = { x:mouseDifference, y:mouseDifference, z:mouseDifference }; var toCameraDistance = getDistanceToCamera(position); var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; - changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); - } - - var newDimensions; - if (proportional) { - var absoluteX = Math.abs(changeInDimensions.x); - var absoluteY = Math.abs(changeInDimensions.y); - var absoluteZ = Math.abs(changeInDimensions.z); - var percentChange = 0; - if (absoluteX > absoluteY && absoluteX > absoluteZ) { - percentChange = changeInDimensions.x / initialProperties.dimensions.x; - percentChange = changeInDimensions.x / initialDimensions.x; - } else if (absoluteY > absoluteZ) { - percentChange = changeInDimensions.y / initialProperties.dimensions.y; - percentChange = changeInDimensions.y / initialDimensions.y; - } else { - percentChange = changeInDimensions.z / initialProperties.dimensions.z; - percentChange = changeInDimensions.z / initialDimensions.z; - } + var dimensionChange = mouseDifference * dimensionsMultiple; + percentChange = dimensionChange / initialDimensions.z; percentChange += 1.0; newDimensions = Vec3.multiply(percentChange, initialDimensions); + newDimensions.x = Math.abs(newDimensions.x); + newDimensions.y = Math.abs(newDimensions.y); + newDimensions.z = Math.abs(newDimensions.z); } else { newDimensions = Vec3.sum(initialDimensions, changeInDimensions); } - + var minimumDimension = directionEnum === STRETCH_DIRECTION.ALL ? STRETCH_ALL_MINIMUM_DIMENSION : STRETCH_MINIMUM_DIMENSION; if (newDimensions.x < minimumDimension) { From 8e5f5cc7965a8a59eab894451ec2a54d5174a568 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Thu, 6 Sep 2018 16:16:43 -0700 Subject: [PATCH 335/744] Moving forgotten links --- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 60c7a26e89..d2a97874b1 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -145,7 +145,7 @@ Item { z: 10 anchors { right: usernameField.right - top: usernameField.top + top: usernameField.bottom topMargin: -19 } @@ -173,7 +173,7 @@ Item { z: 10 anchors { right: passwordField.right - top: passwordField.top + top: passwordField.bottom topMargin: -19 } From acc7f1f168b734e0d58341a241174ee06b1fcd44 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 6 Sep 2018 16:18:01 -0700 Subject: [PATCH 336/744] Moved AnimStats into its own qml panels. Added state machine tracking as well. --- interface/resources/qml/AnimStats.qml | 157 +++++++++++++++++++ interface/resources/qml/Stats.qml | 35 ++++- interface/src/Application.cpp | 4 + interface/src/Menu.cpp | 1 + interface/src/Menu.h | 1 + interface/src/ui/AnimStats.cpp | 141 +++++++++++++++++ interface/src/ui/AnimStats.h | 55 +++++++ interface/src/ui/Stats.cpp | 12 +- interface/src/ui/Stats.h | 12 +- libraries/animation/src/AnimContext.h | 14 ++ libraries/animation/src/AnimStateMachine.cpp | 5 +- libraries/animation/src/AnimStateMachine.h | 1 + libraries/animation/src/Rig.h | 1 + 13 files changed, 427 insertions(+), 12 deletions(-) create mode 100644 interface/resources/qml/AnimStats.qml create mode 100644 interface/src/ui/AnimStats.cpp create mode 100644 interface/src/ui/AnimStats.h diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml new file mode 100644 index 0000000000..35ed3799a6 --- /dev/null +++ b/interface/resources/qml/AnimStats.qml @@ -0,0 +1,157 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.3 +import '.' + +Item { + id: animStats + + anchors.leftMargin: 300 + objectName: "StatsItem" + property int modality: Qt.NonModal + implicitHeight: row.height + implicitWidth: row.width + + Component.onCompleted: { + animStats.parentChanged.connect(fill); + fill(); + } + Component.onDestruction: { + animStats.parentChanged.disconnect(fill); + } + + function fill() { + // This will cause a warning at shutdown, need to find another way to remove + // the warning other than filling the anchors to the parent + anchors.horizontalCenter = parent.horizontalCenter + } + + Hifi.AnimStats { + id: root + objectName: "AnimStats" + implicitHeight: row.height + implicitWidth: row.width + + anchors.horizontalCenter: parent.horizontalCenter + readonly property string bgColor: "#AA111111" + + Row { + id: row + spacing: 8 + + Rectangle { + width: firstCol.width + 8; + height: firstCol.height + 8; + color: root.bgColor; + + Column { + id: firstCol + spacing: 4; x: 4; y: 4; + + StatText { + text: "State Machines:---------------------------------------------------------------------------" + } + ListView { + width: firstCol.width + height: root.animStateMachines.length * 15 + visible: root.animStateMchines.length > 0; + model: root.animStateMachines + delegate: StatText { + text: { + return modelData; + } + } + } + } + } + + Rectangle { + width: secondCol.width + 8 + height: secondCol.height + 8 + color: root.bgColor; + + Column { + id: secondCol + spacing: 4; x: 4; y: 4; + + StatText { + text: "Anim Vars:--------------------------------------------------------------------------------" + } + + ListView { + width: secondCol.width + height: root.animVars.length * 15 + visible: root.animVars.length > 0; + model: root.animVars + delegate: StatText { + text: { + var actualText = modelData.split("|")[1]; + if (actualText) { + return actualText; + } else { + return modelData; + } + } + color: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(1.0, 1.0, 1.0, grayScale); + } + styleColor: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(0.0, 0.0, 0.0, grayScale); + } + } + } + } + } + + Rectangle { + width: thirdCol.width + 8 + height: thirdCol.height + 8 + color: root.bgColor; + + Column { + id: thirdCol + spacing: 4; x: 4; y: 4; + + StatText { + text: "Alpha Values:--------------------------------------------------------------------------" + } + + ListView { + width: thirdCol.width + height: root.animAlphaValues.length * 15 + visible: root.animAlphaValues.length > 0; + model: root.animAlphaValues + delegate: StatText { + text: { + var actualText = modelData.split("|")[1]; + if (actualText) { + return actualText; + } else { + return modelData; + } + } + color: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(1.0, 1.0, 1.0, grayScale); + } + styleColor: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(0.0, 0.0, 0.0, grayScale); + } + } + } + + } + } + } + + Connections { + target: root.parent + onWidthChanged: { + root.x = root.parent.width - root.width; + } + } + } + +} diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 26682d3db8..f74f0539c9 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -192,6 +192,25 @@ Item { StatText { text: "Yaw: " + root.yaw.toFixed(1) } + StatText { + visible: root.animStateMachines.length > 0; + text: "Anim State Machines:" + } + ListView { + width: geoCol.width + height: root.animStateMachines.length * 15 + visible: root.animStateMchines.length > 0; + model: root.animStateMachines + delegate: StatText { + text: { + if (modelData.length > 30) { + return modelData.substring(0, 5) + "..." + modelData.substring(.length - 22); + } else { + return modelData; + } + } + } + } StatText { visible: root.animAlphaValues.length > 0; text: "Anim Alpha Values:" @@ -216,9 +235,11 @@ Item { } color: { var grayScale = parseFloat(modelData.split("|")[0]); - return Qt.rgba(0.3 * grayScale + 0.7, - 0.3 * grayScale + 0.7, - 0.3 * grayScale + 0.7, 1.0); + return Qt.rgba(1.0, 1.0, 1.0, grayScale); + } + styleColor: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(0.0, 0.0, 0.0, grayScale); } } } @@ -246,9 +267,11 @@ Item { } color: { var grayScale = parseFloat(modelData.split("|")[0]); - return Qt.rgba(0.3 * grayScale + 0.7, - 0.3 * grayScale + 0.7, - 0.3 * grayScale + 0.7, 1.0); + return Qt.rgba(1.0, 1.0, 1.0, grayScale); + } + styleColor: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(0.0, 0.0, 0.0, grayScale); } } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2fe67ec5cb..40edcdbd76 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -195,6 +195,7 @@ #include "ui/SnapshotAnimated.h" #include "ui/StandAloneJSConsole.h" #include "ui/Stats.h" +#include "ui/AnimStats.h" #include "ui/UpdateDialog.h" #include "ui/overlays/Overlays.h" #include "ui/DomainConnectionModel.h" @@ -3081,8 +3082,10 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { void Application::onDesktopRootItemCreated(QQuickItem* rootItem) { Stats::show(); + AnimStats::show(); auto surfaceContext = DependencyManager::get()->getSurfaceContext(); surfaceContext->setContextProperty("Stats", Stats::getInstance()); + surfaceContext->setContextProperty("AnimStats", AnimStats::getInstance()); #if !defined(Q_OS_ANDROID) auto offscreenUi = DependencyManager::get(); @@ -4618,6 +4621,7 @@ void Application::idle() { checkChangeCursor(); Stats::getInstance()->updateStats(); + AnimStats::getInstance()->updateStats(); // Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing // details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a6ba983ab5..f7be6d17ef 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -737,6 +737,7 @@ Menu::Menu() { // Developer > Stats addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); + addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AnimStats); // Settings > Enable Speech Control API #if defined(Q_OS_MAC) || defined(Q_OS_WIN) diff --git a/interface/src/Menu.h b/interface/src/Menu.h index c4ea3734f5..031ee2561c 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -197,6 +197,7 @@ namespace MenuOption { const QString SMIEyeTracking = "SMI Eye Tracking"; const QString SparseTextureManagement = "Enable Sparse Texture Management"; const QString Stats = "Show Statistics"; + const QString AnimStats = "Show Animation Stats"; const QString StopAllScripts = "Stop All Scripts"; const QString SuppressShortTimings = "Suppress Timings Less than 10ms"; const QString ThirdPerson = "Third Person"; diff --git a/interface/src/ui/AnimStats.cpp b/interface/src/ui/AnimStats.cpp new file mode 100644 index 0000000000..7b4778e365 --- /dev/null +++ b/interface/src/ui/AnimStats.cpp @@ -0,0 +1,141 @@ +// +// Created by Anthony J. Thibault 2018/08/06 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimStats.h" + +#include +#include +#include "Menu.h" + +HIFI_QML_DEF(AnimStats) + +static AnimStats* INSTANCE{ nullptr }; + +AnimStats* AnimStats::getInstance() { + Q_ASSERT(INSTANCE); + return INSTANCE; +} + +AnimStats::AnimStats(QQuickItem* parent) : QQuickItem(parent) { + INSTANCE = this; +} + +void AnimStats::updateStats(bool force) { + QQuickItem* parent = parentItem(); + if (!force) { + if (!Menu::getInstance()->isOptionChecked(MenuOption::AnimStats)) { + if (parent->isVisible()) { + parent->setVisible(false); + } + return; + } else if (!parent->isVisible()) { + parent->setVisible(true); + } + } + + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager->getMyAvatar(); + auto debugAlphaMap = myAvatar->getSkeletonModel()->getRig().getDebugAlphaMap(); + + // update animation debug alpha values + QStringList newAnimAlphaValues; + qint64 now = usecTimestampNow(); + for (auto& iter : debugAlphaMap) { + QString key = iter.first; + float alpha = std::get<0>(iter.second); + + auto prevIter = _prevDebugAlphaMap.find(key); + if (prevIter != _prevDebugAlphaMap.end()) { + float prevAlpha = std::get<0>(iter.second); + if (prevAlpha != alpha) { + // change detected: reset timer + _animAlphaValueChangedTimers[key] = now; + } + } else { + // new value: start timer + _animAlphaValueChangedTimers[key] = now; + } + + AnimNodeType type = std::get<1>(iter.second); + if (type == AnimNodeType::Clip) { + + // figure out the grayScale color of this line. + const float LIT_TIME = 2.0f; + const float FADE_OUT_TIME = 1.0f; + float grayScale = 0.0f; + float secondsElapsed = (float)(now - _animAlphaValueChangedTimers[key]) / (float)USECS_PER_SECOND; + if (secondsElapsed < LIT_TIME) { + grayScale = 1.0f; + } else if (secondsElapsed < LIT_TIME + FADE_OUT_TIME) { + grayScale = (FADE_OUT_TIME - (secondsElapsed - LIT_TIME)) / FADE_OUT_TIME; + } else { + grayScale = 0.0f; + } + + if (grayScale > 0.0f) { + // append grayScaleColor to start of debug string + newAnimAlphaValues << QString::number(grayScale, 'f', 2) + "|" + key + ": " + QString::number(alpha, 'f', 3); + } + } + } + + _animAlphaValues = newAnimAlphaValues; + _prevDebugAlphaMap = debugAlphaMap; + + emit animAlphaValuesChanged(); + + // update animation anim vars + _animVarsList.clear(); + auto animVars = myAvatar->getSkeletonModel()->getRig().getAnimVars().toDebugMap(); + for (auto& iter : animVars) { + QString key = iter.first; + QString value = iter.second; + + auto prevIter = _prevAnimVars.find(key); + if (prevIter != _prevAnimVars.end()) { + QString prevValue = prevIter->second; + if (value != prevValue) { + // change detected: reset timer + _animVarChangedTimers[key] = now; + } + } else { + // new value: start timer + _animVarChangedTimers[key] = now; + } + + // figure out the grayScale color of this line. + const float LIT_TIME = 2.0f; + const float FADE_OUT_TIME = 0.5f; + float grayScale = 0.0f; + float secondsElapsed = (float)(now - _animVarChangedTimers[key]) / (float)USECS_PER_SECOND; + if (secondsElapsed < LIT_TIME) { + grayScale = 1.0f; + } else if (secondsElapsed < LIT_TIME + FADE_OUT_TIME) { + grayScale = (FADE_OUT_TIME - (secondsElapsed - LIT_TIME)) / FADE_OUT_TIME; + } else { + grayScale = 0.0f; + } + + if (grayScale > 0.0f) { + // append grayScaleColor to start of debug string + _animVarsList << QString::number(grayScale, 'f', 2) + "|" + key + ": " + value; + } + } + _prevAnimVars = animVars; + emit animVarsChanged(); + + // animation state machines + _animStateMachines.clear(); + auto stateMachineMap = myAvatar->getSkeletonModel()->getRig().getStateMachineMap(); + for (auto& iter : stateMachineMap) { + _animStateMachines << iter.second; + } + emit animStateMachinesChanged(); +} + + diff --git a/interface/src/ui/AnimStats.h b/interface/src/ui/AnimStats.h new file mode 100644 index 0000000000..1a6795b498 --- /dev/null +++ b/interface/src/ui/AnimStats.h @@ -0,0 +1,55 @@ +// +// Created by Anthony J. Thibault 2018/08/06 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AnimStats_h +#define hifi_AnimStats_h + +#include +#include + +class AnimStats : public QQuickItem { + Q_OBJECT + HIFI_QML_DECL + + Q_PROPERTY(QStringList animAlphaValues READ animAlphaValues NOTIFY animAlphaValuesChanged) + Q_PROPERTY(QStringList animVars READ animVars NOTIFY animVarsChanged) + Q_PROPERTY(QStringList animStateMachines READ animStateMachines NOTIFY animStateMachinesChanged) + +public: + static AnimStats* getInstance(); + + AnimStats(QQuickItem* parent = nullptr); + + void updateStats(bool force = false); + + QStringList animAlphaValues() { return _animAlphaValues; } + QStringList animVars() { return _animVarsList; } + QStringList animStateMachines() { return _animStateMachines; } + +public slots: + void forceUpdateStats() { updateStats(true); } + +signals: + + void animAlphaValuesChanged(); + void animVarsChanged(); + void animStateMachinesChanged(); + +private: + QStringList _animAlphaValues; + AnimContext::DebugAlphaMap _prevDebugAlphaMap; // alpha values from previous frame + std::map _animAlphaValueChangedTimers; // last time alpha value has changed + + QStringList _animVarsList; + std::map _prevAnimVars; // anim vars from previous frame + std::map _animVarChangedTimers; // last time animVar value has changed. + + QStringList _animStateMachines; +}; + +#endif // hifi_AnimStats_h diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 62e44645b9..bfb83f10f8 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -239,7 +239,7 @@ void Stats::updateStats(bool force) { if (secondsElapsed < LIT_TIME) { grayScale = 1.0f; } else if (secondsElapsed < LIT_TIME + FADE_OUT_TIME) { - grayScale = (LIT_TIME - (secondsElapsed - FADE_OUT_TIME)) / LIT_TIME; + grayScale = (FADE_OUT_TIME - (secondsElapsed - LIT_TIME)) / FADE_OUT_TIME; } else { grayScale = 0.0f; } @@ -283,7 +283,7 @@ void Stats::updateStats(bool force) { if (secondsElapsed < LIT_TIME) { grayScale = 1.0f; } else if (secondsElapsed < LIT_TIME + FADE_OUT_TIME) { - grayScale = (LIT_TIME - (secondsElapsed - FADE_OUT_TIME)) / LIT_TIME; + grayScale = (FADE_OUT_TIME - (secondsElapsed - LIT_TIME)) / FADE_OUT_TIME; } else { grayScale = 0.0f; } @@ -296,6 +296,14 @@ void Stats::updateStats(bool force) { _prevAnimVars = animVars; emit animVarsChanged(); + // animation state machines + _animStateMachines.clear(); + auto stateMachineMap = myAvatar->getSkeletonModel()->getRig().getStateMachineMap(); + for (auto& iter : stateMachineMap) { + _animStateMachines << iter.second; + } + emit animStateMachinesChanged(); + glm::vec3 avatarPos = myAvatar->getWorldPosition(); STAT_UPDATE(position, QVector3D(avatarPos.x, avatarPos.y, avatarPos.z)); STAT_UPDATE_FLOAT(speed, glm::length(myAvatar->getWorldVelocity()), 0.01f); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 3e71c75da1..1febfff8b5 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -184,6 +184,7 @@ class Stats : public QQuickItem { HIFI_QML_DECL Q_PROPERTY(bool expanded READ isExpanded WRITE setExpanded NOTIFY expandedChanged) Q_PROPERTY(bool timingExpanded READ isTimingExpanded NOTIFY timingExpandedChanged) + Q_PROPERTY(QString monospaceFont READ monospaceFont CONSTANT) STATS_PROPERTY(int, serverCount, 0) @@ -294,6 +295,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(float, avatarSimulationTime, 0) Q_PROPERTY(QStringList animAlphaValues READ animAlphaValues NOTIFY animAlphaValuesChanged) Q_PROPERTY(QStringList animVars READ animVars NOTIFY animVarsChanged) + Q_PROPERTY(QStringList animStateMachines READ animStateMachines NOTIFY animStateMachinesChanged) STATS_PROPERTY(int, stylusPicksCount, 0) STATS_PROPERTY(int, rayPicksCount, 0) @@ -329,6 +331,7 @@ public: QStringList downloadUrls () { return _downloadUrls; } QStringList animAlphaValues() { return _animAlphaValues; } QStringList animVars() { return _animVarsList; } + QStringList animStateMachines() { return _animStateMachines; } public slots: void forceUpdateStats() { updateStats(true); } @@ -1032,6 +1035,7 @@ signals: void animAlphaValuesChanged(); void animVarsChanged(); + void animStateMachinesChanged(); /**jsdoc * Triggered when the value of the rectifiedTextureCount property changes. @@ -1333,15 +1337,17 @@ private: bool _showGameUpdateStats{ false }; QString _monospaceFont; const AudioIOStats* _audioStats; - QStringList _downloadUrls = QStringList(); + QStringList _downloadUrls; - QStringList _animAlphaValues = QStringList(); + QStringList _animAlphaValues; AnimContext::DebugAlphaMap _prevDebugAlphaMap; // alpha values from previous frame std::map _animAlphaValueChangedTimers; // last time alpha value has changed - QStringList _animVarsList = QStringList(); + QStringList _animVarsList; std::map _prevAnimVars; // anim vars from previous frame std::map _animVarChangedTimers; // last time animVar value has changed. + + QStringList _animStateMachines; }; #endif // hifi_Stats_h diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index ecfb94a28e..b30421ae84 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -64,6 +64,19 @@ public: return _debugAlphaMap; } + using DebugStateMachineMapValue = QString; + using DebugStateMachineMap = std::map; + + void addStateMachineInfo(const QString& stateMachineName, const QString& currentState, const QString& previousState, bool duringInterp, float alpha) const { + if (duringInterp) { + _stateMachineMap[stateMachineName] = QString("%1: %2 -> %3 (%4)").arg(stateMachineName).arg(previousState).arg(currentState).arg(QString::number(alpha, 'f', 2)); + } else { + _stateMachineMap[stateMachineName] = QString("%1: %2").arg(stateMachineName).arg(currentState); + } + } + + const DebugStateMachineMap& getStateMachineMap() const { return _stateMachineMap; } + protected: bool _enableDebugDrawIKTargets { false }; @@ -74,6 +87,7 @@ protected: // used for debugging internal state of animation system. mutable DebugAlphaMap _debugAlphaMap; + mutable DebugStateMachineMap _stateMachineMap; }; #endif // hifi_AnimContext_h diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 00ae5e6d39..7f46cd614a 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -87,10 +87,13 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co } processOutputJoints(triggersOut); + context.addStateMachineInfo(_id, _currentState->getID(), _previousState->getID(), _duringInterp, _alpha); + return _poses; } void AnimStateMachine::setCurrentState(State::Pointer state) { + _previousState = _currentState ? _currentState : state; _currentState = state; } @@ -135,7 +138,7 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, const AnimCon qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget << "interpType = " << (int)_interpType; #endif - _currentState = desiredState; + setCurrentState(desiredState); } AnimStateMachine::State::Pointer AnimStateMachine::evaluateTransitions(const AnimVariantMap& animVars) const { diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index 8e1e1b7bd8..713c659a1d 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -140,6 +140,7 @@ protected: AnimPoseVec _nextPoses; State::Pointer _currentState; + State::Pointer _previousState; std::vector _states; QString _currentStateVar; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 95a4b6f40d..48f00d4e5d 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -225,6 +225,7 @@ public: // used to debug animation playback const AnimContext::DebugAlphaMap& getDebugAlphaMap() const { return _lastContext.getDebugAlphaMap(); } const AnimVariantMap& getAnimVars() const { return _lastAnimVars; } + const AnimContext::DebugStateMachineMap& getStateMachineMap() const { return _lastContext.getStateMachineMap(); } void toggleSmoothPoleVectors() { _smoothPoleVectors = !_smoothPoleVectors; }; signals: From 9e0c21065e0de0d3b75e0f213959d27ca61f6013 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 6 Sep 2018 15:47:23 -0700 Subject: [PATCH 337/744] Stop sending avatars when over bandwidth quota --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index a61f65ffb0..5dd250fba7 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -425,6 +425,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // NOTE: Here's where we determine if we are over budget and drop to bare minimum data int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame; + if (overBudget) { + _stats.overBudgetAvatars += remainingAvatars + 1; + overBudgetAvatars += remainingAvatars + 1; + break; + } auto startAvatarDataPacking = chrono::high_resolution_clock::now(); From f59168e1c8b61a143cd0b0de8b672b36d5b00493 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 6 Sep 2018 16:31:31 -0700 Subject: [PATCH 338/744] make sure ignoredNode is available before sending packet --- assignment-client/src/avatars/AvatarMixer.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index edbba20dc7..561afee296 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -654,6 +654,15 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer if (addToIgnore) { senderNode->addIgnoredNode(ignoredUUID); + + if (ignoredNode) { + // send a reliable kill packet to remove the sending avatar for the ignored avatar + auto killPacket = NLPacket::create(PacketType::KillAvatar, + NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); + killPacket->write(senderNode->getUUID().toRfc4122()); + killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected); + nodeList->sendPacket(std::move(killPacket), *ignoredNode); + } } else { senderNode->removeIgnoredNode(ignoredUUID); } From 836f810c419b1981da284aead6df1187910a7bc0 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 6 Sep 2018 14:11:42 -0700 Subject: [PATCH 339/744] Have OverlayTransformNode and similar define scale relative to nestable scale on creation time, and refactor code --- .../src/raypick/PickScriptingInterface.cpp | 4 ++ .../src/ui/overlays/OverlayTransformNode.cpp | 22 +--------- .../src/ui/overlays/OverlayTransformNode.h | 11 ++--- .../avatars-renderer/AvatarTransformNode.cpp | 22 +--------- .../avatars-renderer/AvatarTransformNode.h | 11 ++--- .../entities/src/EntityTransformNode.cpp | 22 +--------- libraries/entities/src/EntityTransformNode.h | 11 ++--- .../shared/src/NestableTransformNode.cpp | 22 +--------- libraries/shared/src/NestableTransformNode.h | 44 +++++++++++++++++-- 9 files changed, 61 insertions(+), 108 deletions(-) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index b892c6da8e..247df2b11e 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -383,6 +383,10 @@ std::shared_ptr PickScriptingInterface::createTransformNode(const if (propMap["parentJointIndex"].isValid()) { parentJointIndex = propMap["parentJointIndex"].toInt(); } + glm::vec3 baseScale = glm::vec3(1); + if (propMap["baseScale"].isValid()) { + baseScale = vec3FromVariant(propMap["baseScale"]); + } auto sharedNestablePointer = nestablePointer.lock(); if (success && sharedNestablePointer) { NestableType nestableType = sharedNestablePointer->getNestableType(); diff --git a/interface/src/ui/overlays/OverlayTransformNode.cpp b/interface/src/ui/overlays/OverlayTransformNode.cpp index d0a6618e37..fa499b3ce3 100644 --- a/interface/src/ui/overlays/OverlayTransformNode.cpp +++ b/interface/src/ui/overlays/OverlayTransformNode.cpp @@ -7,24 +7,6 @@ // #include "OverlayTransformNode.h" -OverlayTransformNode::OverlayTransformNode(std::weak_ptr overlay, int jointIndex) : - _overlay(overlay), - _jointIndex(jointIndex) -{} - -Transform OverlayTransformNode::getTransform() { - auto overlay = _overlay.lock(); - if (!overlay) { - return Transform(); - } - - bool success; - Transform jointWorldTransform = overlay->getTransform(_jointIndex, success); - if (!success) { - return Transform(); - } - - jointWorldTransform.setScale(overlay->getBounds().getScale()); - - return jointWorldTransform; +glm::vec3 BaseNestableTransformNode::getActualScale(const std::shared_ptr& nestablePointer) const { + return nestablePointer->getBounds().getScale(); } \ No newline at end of file diff --git a/interface/src/ui/overlays/OverlayTransformNode.h b/interface/src/ui/overlays/OverlayTransformNode.h index b9ea9f72c4..11c3415828 100644 --- a/interface/src/ui/overlays/OverlayTransformNode.h +++ b/interface/src/ui/overlays/OverlayTransformNode.h @@ -8,19 +8,14 @@ #ifndef hifi_OverlayTransformNode_h #define hifi_OverlayTransformNode_h -#include "TransformNode.h" +#include "NestableTransformNode.h" #include "Base3DOverlay.h" // For 3D overlays only -class OverlayTransformNode : public TransformNode { +class OverlayTransformNode : public BaseNestableTransformNode { public: - OverlayTransformNode(std::weak_ptr overlay, int jointIndex); - Transform getTransform() override; - -protected: - std::weak_ptr _overlay; - int _jointIndex; + OverlayTransformNode(std::weak_ptr spatiallyNestable, int jointIndex) : BaseNestableTransformNode(spatiallyNestable, jointIndex) {}; }; #endif // hifi_OverlayTransformNode_h \ No newline at end of file diff --git a/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp index fc5ba5bc1a..bc24b8a570 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.cpp @@ -7,24 +7,6 @@ // #include "AvatarTransformNode.h" -AvatarTransformNode::AvatarTransformNode(AvatarWeakPointer avatar, int jointIndex) : - _avatar(avatar), - _jointIndex(jointIndex) -{} - -Transform AvatarTransformNode::getTransform() { - auto avatar = _avatar.lock(); - if (!avatar) { - return Transform(); - } - - bool success; - Transform jointWorldTransform = avatar->getTransform(_jointIndex, success); - if (!success) { - return Transform(); - } - - jointWorldTransform.setScale(avatar->scaleForChildren()); - - return jointWorldTransform; +glm::vec3 BaseNestableTransformNode::getActualScale(const std::shared_ptr& nestablePointer) const { + return nestablePointer->scaleForChildren(); } \ No newline at end of file diff --git a/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.h b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.h index 03191b8dbe..183e4ab05c 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.h +++ b/libraries/avatars-renderer/src/avatars-renderer/AvatarTransformNode.h @@ -8,18 +8,13 @@ #ifndef hifi_AvatarTransformNode_h #define hifi_AvatarTransformNode_h -#include "TransformNode.h" +#include "NestableTransformNode.h" #include "Avatar.h" -class AvatarTransformNode : public TransformNode { +class AvatarTransformNode : public BaseNestableTransformNode { public: - AvatarTransformNode(AvatarWeakPointer avatar, int jointIndex); - Transform getTransform() override; - -protected: - AvatarWeakPointer _avatar; - int _jointIndex; + AvatarTransformNode(std::weak_ptr spatiallyNestable, int jointIndex) : BaseNestableTransformNode(spatiallyNestable, jointIndex) {}; }; #endif // hifi_AvatarTransformNode_h \ No newline at end of file diff --git a/libraries/entities/src/EntityTransformNode.cpp b/libraries/entities/src/EntityTransformNode.cpp index 5b0a690619..35f4d8e52f 100644 --- a/libraries/entities/src/EntityTransformNode.cpp +++ b/libraries/entities/src/EntityTransformNode.cpp @@ -7,24 +7,6 @@ // #include "EntityTransformNode.h" -EntityTransformNode::EntityTransformNode(EntityItemWeakPointer entity, int jointIndex) : - _entity(entity), - _jointIndex(jointIndex) -{} - -Transform EntityTransformNode::getTransform() { - auto entity = _entity.lock(); - if (!entity) { - return Transform(); - } - - bool success; - Transform jointWorldTransform = entity->getTransform(_jointIndex, success); - if (!success) { - return Transform(); - } - - jointWorldTransform.setScale(entity->getScaledDimensions()); - - return jointWorldTransform; +glm::vec3 BaseNestableTransformNode::getActualScale(const std::shared_ptr& nestablePointer) const { + return nestablePointer->getScaledDimensions(); } \ No newline at end of file diff --git a/libraries/entities/src/EntityTransformNode.h b/libraries/entities/src/EntityTransformNode.h index b7388600df..07818f99f3 100644 --- a/libraries/entities/src/EntityTransformNode.h +++ b/libraries/entities/src/EntityTransformNode.h @@ -8,18 +8,13 @@ #ifndef hifi_EntityTransformNode_h #define hifi_EntityTransformNode_h -#include "TransformNode.h" +#include "NestableTransformNode.h" #include "EntityItem.h" -class EntityTransformNode : public TransformNode { +class EntityTransformNode : public BaseNestableTransformNode { public: - EntityTransformNode(EntityItemWeakPointer entity, int jointIndex); - Transform getTransform() override; - -protected: - EntityItemWeakPointer _entity; - int _jointIndex; + EntityTransformNode(std::weak_ptr spatiallyNestable, int jointIndex) : BaseNestableTransformNode(spatiallyNestable, jointIndex) {}; }; #endif // hifi_EntityTransformNode_h \ No newline at end of file diff --git a/libraries/shared/src/NestableTransformNode.cpp b/libraries/shared/src/NestableTransformNode.cpp index 17456d69ce..9da2c85aa3 100644 --- a/libraries/shared/src/NestableTransformNode.cpp +++ b/libraries/shared/src/NestableTransformNode.cpp @@ -8,24 +8,6 @@ #include "NestableTransformNode.h" -NestableTransformNode::NestableTransformNode(SpatiallyNestableWeakPointer spatiallyNestable, int jointIndex) : - _spatiallyNestable(spatiallyNestable), - _jointIndex(jointIndex) -{ -} - -Transform NestableTransformNode::getTransform() { - auto nestable = _spatiallyNestable.lock(); - if (!nestable) { - return Transform(); - } - - bool success; - Transform jointWorldTransform = nestable->getTransform(_jointIndex, success); - - if (success) { - return jointWorldTransform; - } else { - return Transform(); - } +glm::vec3 BaseNestableTransformNode::getActualScale(const std::shared_ptr& nestablePointer) const { + return nestablePointer->getAbsoluteJointScaleInObjectFrame(_jointIndex); } \ No newline at end of file diff --git a/libraries/shared/src/NestableTransformNode.h b/libraries/shared/src/NestableTransformNode.h index 131de9e786..21efd1c690 100644 --- a/libraries/shared/src/NestableTransformNode.h +++ b/libraries/shared/src/NestableTransformNode.h @@ -12,14 +12,50 @@ #include "SpatiallyNestable.h" -class NestableTransformNode : public TransformNode { +template +class BaseNestableTransformNode : public TransformNode { public: - NestableTransformNode(SpatiallyNestableWeakPointer spatiallyNestable, int jointIndex); - Transform getTransform() override; + BaseNestableTransformNode(std::weak_ptr spatiallyNestable, int jointIndex) : + _spatiallyNestable(spatiallyNestable), + _jointIndex(jointIndex) { + auto nestablePointer = _spatiallyNestable.lock(); + if (nestablePointer) { + glm::vec3 nestableDimensions = getActualScale(nestablePointer); + if (!glm::any(glm::equal(nestableDimensions, glm::vec3(0.0f)))) { + _baseScale = nestableDimensions; + } + } + } + + Transform getTransform() { + std::shared_ptr nestable = _spatiallyNestable.lock(); + if (!nestable) { + return Transform(); + } + + bool success; + Transform jointWorldTransform = nestable->getTransform(_jointIndex, success); + + if (!success) { + return Transform(); + } + + jointWorldTransform.setScale(getActualScale(nestable) / _baseScale); + + return jointWorldTransform; + } + + glm::vec3 getActualScale(const std::shared_ptr& nestablePointer) const; protected: - SpatiallyNestableWeakPointer _spatiallyNestable; + std::weak_ptr _spatiallyNestable; int _jointIndex; + glm::vec3 _baseScale { 1.0f }; +}; + +class NestableTransformNode : public BaseNestableTransformNode { +public: + NestableTransformNode(std::weak_ptr spatiallyNestable, int jointIndex) : BaseNestableTransformNode(spatiallyNestable, jointIndex) {}; }; #endif // hifi_NestableTransformNode_h \ No newline at end of file From 92210f28b51403b9ff2a0783c3d9222a46638dd1 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 6 Sep 2018 16:33:30 -0700 Subject: [PATCH 340/744] cleanup and use correct avatar bounding box --- .../src/avatars/AvatarMixerClientData.h | 1 - assignment-client/src/avatars/AvatarMixerSlave.cpp | 14 ++++++-------- libraries/avatars/src/AvatarData.h | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 81a4a58769..64b1ea3edf 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -91,7 +91,6 @@ public: void loadJSONStats(QJsonObject& jsonObject) const; glm::vec3 getPosition() const { return _avatar ? _avatar->getWorldPosition() : glm::vec3(0); } - glm::vec3 getGlobalBoundingBoxCorner() const { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); } bool isRadiusIgnoring(const QUuid& other) const { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); } void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); } void removeFromRadiusIgnoringSet(const QUuid& other); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 3e83a78341..411f77459f 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -213,12 +213,13 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { } AABox computeBubbleBox(const AvatarData& avatar, float bubbleExpansionFactor) { - glm::vec3 position = avatar.getClientGlobalPosition(); - glm::vec3 scale = 2.0f * (avatar.getClientGlobalPosition()- avatar.getGlobalBoundingBoxCorner()); + AABox box = avatar.getGlobalBoundingBox(); + glm::vec3 scale = box.getScale(); scale *= bubbleExpansionFactor; const glm::vec3 MIN_BUBBLE_SCALE(0.3f, 1.3f, 0.3); scale = glm::max(scale, MIN_BUBBLE_SCALE); - return AABox(position - 0.5f * scale, scale); + box.setScaleStayCentered(glm::max(scale, MIN_BUBBLE_SCALE)); + return box; } void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { @@ -295,7 +296,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) : _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {} glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); } float getRadius() const override { - glm::vec3 nodeBoxHalfScale = _avatar->getClientGlobalPosition() - _avatar->getGlobalBoundingBoxCorner(); + glm::vec3 nodeBoxHalfScale = 0.5f * _avatar->getGlobalBoundingBox().getScale(); return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); } uint64_t getTimestamp() const override { @@ -433,10 +434,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } // determine if avatar is in view which determines how much data to send - glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition(); - glm::vec3 otherNodeBoxScale = otherPosition - otherNodeData->getGlobalBoundingBoxCorner(); - AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - bool isInView = nodeData->otherAvatarInView(otherNodeBox); + bool isInView = nodeData->otherAvatarInView(otherAvatar->getGlobalBoundingBox()); // start a new segment in the PacketList for this avatar avatarPacketList->startSegment(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 0474d07acd..9146340c4d 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1095,7 +1095,7 @@ public: void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); glm::vec3 getClientGlobalPosition() const { return _globalPosition; } - glm::vec3 getGlobalBoundingBoxCorner() const { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; } + AABox getGlobalBoundingBox() const { return AABox(_globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); } /**jsdoc * @function MyAvatar.getAvatarEntityData From 23a6abbaca6f36726d81c3072a74bc0b9f79ce6b Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 6 Sep 2018 16:47:09 -0700 Subject: [PATCH 341/744] Update jsdocs to reflect new collision pick scaling behavior --- interface/src/raypick/PickScriptingInterface.cpp | 6 +++--- libraries/shared/src/RegisteredMetaTypes.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 247df2b11e..41669c9f0c 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -265,11 +265,11 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti * @typedef {object} Picks.CollisionPickProperties * @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results. * @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR. -* @property {Shape} shape - The information about the collision region's size and shape. Dimensions are relative to a parent if defined. +* @property {Shape} shape - The information about the collision region's size and shape. Dimensions are in world space, but will scale with the parent if defined. * @property {Vec3} position - The position of the collision region, relative to a parent if defined. * @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined. -* @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region, relative to a parent if defined. -* For overlay and entity parents, this is relative to the parent's largest dimension. +* @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region. +* The depth is measured in world space, but will scale with the parent if defined. * @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay. * @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) * @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 79adce0a39..d59c58def8 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -259,11 +259,11 @@ public: * A CollisionRegion defines a volume for checking collisions in the physics simulation. * @typedef {object} CollisionRegion -* @property {Shape} shape - The information about the collision region's size and shape. Dimensions are relative to a parent if defined. +* @property {Shape} shape - The information about the collision region's size and shape. Dimensions are in world space, but will scale with the parent if defined. * @property {Vec3} position - The position of the collision region, relative to a parent if defined. * @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined. -* @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region, relative to a parent if defined. -* For overlay and entity parents, this is relative to the parent's largest dimension. +* @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region. +* The depth is measured in world space, but will scale with the parent if defined. * @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay. * @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) * @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. From ed7f993c0dc257132c860d415247215d6349d198 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 6 Sep 2018 16:55:27 -0700 Subject: [PATCH 342/744] avatar mixer and manager perf improvements and cleanup --- .../src/avatars/AvatarMixerSlave.cpp | 45 +++++++++---------- interface/src/avatar/AvatarManager.cpp | 40 ++++++++--------- libraries/avatars/src/AvatarData.cpp | 17 +++---- libraries/avatars/src/AvatarData.h | 2 - libraries/avatars/src/AvatarHashMap.cpp | 1 - libraries/shared/src/PrioritySortUtil.h | 9 ++-- 6 files changed, 50 insertions(+), 64 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 8afd7d73ee..705d660a2e 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -261,8 +261,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // FIXME - find a way to not send the sessionID for every avatar int minimumBytesPerAvatar = AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID; - int overBudgetAvatars = 0; - // keep track of the number of other avatars held back in this frame int numAvatarsHeldBack = 0; @@ -287,7 +285,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // compute node bounding box const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically - AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR ); + AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR); class SortableAvatar: public PrioritySortUtil::Sortable { public: @@ -296,13 +294,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) : _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {} glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); } float getRadius() const override { - glm::vec3 nodeBoxHalfScale = 0.5f * _avatar->getGlobalBoundingBox().getScale(); - return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); + glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale(); + return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z)); } uint64_t getTimestamp() const override { return _lastEncodeTime; } - const AvatarData* getAvatar() const { return _avatar; } const Node* getNode() const { return _node; } private: @@ -412,13 +409,19 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map + AvatarData::AvatarDataDetail detail; + // NOTE: Here's where we determine if we are over budget and drop to bare minimum data int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame; if (overBudget) { - _stats.overBudgetAvatars += remainingAvatars + 1; - overBudgetAvatars += remainingAvatars + 1; - break; + if (PALIsOpen) { + _stats.overBudgetAvatars++; + detail = AvatarData::PALMinimum; + } else { + _stats.overBudgetAvatars += remainingAvatars; + break; + } } auto startAvatarDataPacking = chrono::high_resolution_clock::now(); @@ -439,23 +442,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } // determine if avatar is in view which determines how much data to send - bool isInView = nodeData->otherAvatarInView(otherAvatar->getGlobalBoundingBox()); + bool isInView = sortedAvatar.getPriority() > OUT_OF_VIEW_THRESHOLD; - // start a new segment in the PacketList for this avatar - avatarPacketList->startSegment(); - - AvatarData::AvatarDataDetail detail; - - if (overBudget) { - overBudgetAvatars++; - _stats.overBudgetAvatars++; - detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::NoData; - } else if (!isInView) { + if (!isInView) { detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; nodeData->incrementAvatarOutOfView(); - } else { - detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO - ? AvatarData::SendAllData : AvatarData::CullSmallData; + } else if (!overBudget) { + detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; nodeData->incrementAvatarInView(); } @@ -503,8 +496,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } if (includeThisAvatar) { + // start a new segment in the PacketList for this avatar + avatarPacketList->startSegment(); numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += avatarPacketList->write(bytes); + avatarPacketList->endSegment(); if (detail != AvatarData::NoData) { _stats.numOthersIncluded++; @@ -522,14 +518,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // It would be nice if we could tweak its future sort priority to put it at the back of the list. } - avatarPacketList->endSegment(); - auto endAvatarDataPacking = chrono::high_resolution_clock::now(); _stats.avatarDataPackingElapsedTime += (quint64) chrono::duration_cast(endAvatarDataPacking - startAvatarDataPacking).count(); // use helper to add any changed traits to our packet list traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); + remainingAvatars--; } quint64 startPacketSending = usecTimestampNow(); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index e9486b9def..1bc22ea98c 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -166,29 +166,29 @@ float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QStri } void AvatarManager::updateOtherAvatars(float deltaTime) { - // lock the hash for read to check the size - QReadLocker lock(&_hashLock); - if (_avatarHash.size() < 2 && _avatarsToFade.isEmpty()) { - return; + { + // lock the hash for read to check the size + QReadLocker lock(&_hashLock); + if (_avatarHash.size() < 2 && _avatarsToFade.isEmpty()) { + return; + } } - lock.unlock(); PerformanceTimer perfTimer("otherAvatars"); class SortableAvatar: public PrioritySortUtil::Sortable { public: SortableAvatar() = delete; - SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {} + SortableAvatar(const std::shared_ptr& avatar) : _avatar(avatar) {} glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } - float getRadius() const override { return std::static_pointer_cast(_avatar)->getBoundingRadius(); } - uint64_t getTimestamp() const override { return std::static_pointer_cast(_avatar)->getLastRenderUpdateTime(); } - AvatarSharedPointer getAvatar() const { return _avatar; } + float getRadius() const override { return _avatar->getBoundingRadius(); } + uint64_t getTimestamp() const override { return _avatar->getLastRenderUpdateTime(); } + std::shared_ptr getAvatar() const { return _avatar; } private: - AvatarSharedPointer _avatar; + std::shared_ptr _avatar; }; auto avatarMap = getHashCopy(); - AvatarHash::iterator itr = avatarMap.begin(); const auto& views = qApp->getConicalViews(); PrioritySortUtil::PriorityQueue sortedAvatars(views, @@ -197,22 +197,24 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { AvatarData::_avatarSortCoefficientAge); sortedAvatars.reserve(avatarMap.size() - 1); // don't include MyAvatar - // sort + // Build vector and compute priorities + auto nodeList = DependencyManager::get(); + AvatarHash::iterator itr = avatarMap.begin(); while (itr != avatarMap.end()) { const auto& avatar = std::static_pointer_cast(*itr); // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. // DO NOT update or fade out uninitialized Avatars - if (avatar != _myAvatar && avatar->isInitialized()) { + if (avatar != _myAvatar && avatar->isInitialized() && !nodeList->isPersonalMutingNode(avatar->getID())) { sortedAvatars.push(SortableAvatar(avatar)); } ++itr; } + // Sort const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); // process in sorted order uint64_t startTime = usecTimestampNow(); - const uint64_t UPDATE_BUDGET = 2000; // usec - uint64_t updateExpiry = startTime + UPDATE_BUDGET; + uint64_t updateExpiry = startTime + MAX_UPDATE_AVATARS_TIME_BUDGET; int numAvatarsUpdated = 0; int numAVatarsNotUpdated = 0; @@ -231,18 +233,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { avatar->updateOrbPosition(); } - bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); - if (ignoring) { - continue; - } - // for ALL avatars... if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); } avatar->animateScaleChanges(deltaTime); - const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY; uint64_t now = usecTimestampNow(); if (now < updateExpiry) { // we're within budget @@ -263,7 +259,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // no time to simulate, but we take the time to count how many were tragically missed while (it != sortedAvatarVector.end()) { const SortableAvatar& newSortData = *it; - const auto newAvatar = std::static_pointer_cast(newSortData.getAvatar()); + const auto& newAvatar = newSortData.getAvatar(); bool inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; // Once we reach an avatar that's not in view, all avatars after it will also be out of view if (!inView) { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 13adc803dd..038d08145a 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -837,7 +837,6 @@ const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSa // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { - // lazily allocate memory for HeadData in case we're not an Avatar instance lazyInitHeadData(); @@ -889,7 +888,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; if (_globalPosition != newValue) { _globalPosition = newValue; - _globalPositionChanged = usecTimestampNow(); + _globalPositionChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); int numBytesRead = sourceBuffer - startSection; @@ -913,11 +912,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if (_globalBoundingBoxDimensions != newDimensions) { _globalBoundingBoxDimensions = newDimensions; - _avatarBoundingBoxChanged = usecTimestampNow(); + _avatarBoundingBoxChanged = now; } if (_globalBoundingBoxOffset != newOffset) { _globalBoundingBoxOffset = newOffset; - _avatarBoundingBoxChanged = usecTimestampNow(); + _avatarBoundingBoxChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); @@ -1018,7 +1017,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans); if (_sensorToWorldMatrixCache.get() != sensorToWorldMatrix) { _sensorToWorldMatrixCache.set(sensorToWorldMatrix); - _sensorToWorldMatrixChanged = usecTimestampNow(); + _sensorToWorldMatrixChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix); int numBytesRead = sourceBuffer - startSection; @@ -1075,7 +1074,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); if (somethingChanged) { - _additionalFlagsChanged = usecTimestampNow(); + _additionalFlagsChanged = now; } int numBytesRead = sourceBuffer - startSection; _additionalFlagsRate.increment(numBytesRead); @@ -1095,7 +1094,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if ((getParentID() != newParentID) || (getParentJointIndex() != parentInfo->parentJointIndex)) { SpatiallyNestable::setParentID(newParentID); SpatiallyNestable::setParentJointIndex(parentInfo->parentJointIndex); - _parentChanged = usecTimestampNow(); + _parentChanged = now; } int numBytesRead = sourceBuffer - startSection; @@ -1144,8 +1143,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { int numBytesRead = sourceBuffer - startSection; _faceTrackerRate.increment(numBytesRead); _faceTrackerUpdateRate.increment(); - } else { - _headData->_blendshapeCoefficients.fill(0, _headData->_blendshapeCoefficients.size()); } if (hasJointData) { @@ -2820,8 +2817,6 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra value.extraInfo = object.property("extraInfo").toVariant().toMap(); } -const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f; - float AvatarData::_avatarSortCoefficientSize { 8.0f }; float AvatarData::_avatarSortCoefficientCenter { 4.0f }; float AvatarData::_avatarSortCoefficientAge { 1.0f }; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 9146340c4d..cd6440ae5d 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1167,8 +1167,6 @@ public: // A method intended to be overriden by MyAvatar for polling orientation for network transmission. virtual glm::quat getOrientationOutbound() const; - static const float OUT_OF_VIEW_PENALTY; - // TODO: remove this HACK once we settle on optimal sort coefficients // These coefficients exposed for fine tuning the sort priority for transfering new _jointData to the render pipeline. static float _avatarSortCoefficientSize; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index c437b56f32..be2ce3d397 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -21,7 +21,6 @@ #include "AvatarLogging.h" #include "AvatarTraits.h" - void AvatarReplicas::addReplica(const QUuid& parentID, AvatarSharedPointer replica) { if (parentID == QUuid()) { return; diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 075de13d9d..27f6b193ba 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -18,6 +18,9 @@ // PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. +const float OUT_OF_VIEW_PENALTY = -10.0f; +const float OUT_OF_VIEW_THRESHOLD = 0.5f * OUT_OF_VIEW_PENALTY; + namespace PrioritySortUtil { constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; @@ -93,17 +96,16 @@ namespace PrioritySortUtil { const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance) float radius = glm::max(thing.getRadius(), MIN_RADIUS); // Other item's angle from view centre: - float cosineAngle = (glm::dot(offset, view.getDirection()) / distance); + float cosineAngle = glm::dot(offset, view.getDirection()) / distance; float age = float((_usecCurrentTime - thing.getTimestamp()) / USECS_PER_SECOND); // the "age" term accumulates at the sum of all weights - float angularSize = glm::max(radius, MIN_RADIUS) / distance; + float angularSize = radius / distance; float priority = (_angularWeight * angularSize + _centerWeight * cosineAngle) * (age + 1.0f) + _ageWeight * age; // decrement priority of things outside keyhole if (distance - radius > view.getRadius()) { if (!view.intersects(offset, distance, radius)) { - constexpr float OUT_OF_VIEW_PENALTY = -10.0f; priority += OUT_OF_VIEW_PENALTY; } } @@ -122,5 +124,6 @@ namespace PrioritySortUtil { // for now we're keeping hard-coded sorted time budgets in one spot const uint64_t MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2000; // usec const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1000; // usec +const uint64_t MAX_UPDATE_AVATARS_TIME_BUDGET = 2000; // usec #endif // hifi_PrioritySortUtil_h From 2f9119529a67209d0adcfda189ddd6859d52a80f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 6 Sep 2018 17:06:17 -0700 Subject: [PATCH 343/744] editing the interstitial page --- interface/src/Application.cpp | 7 ++----- interface/src/Menu.cpp | 1 - scripts/defaultScripts.js | 3 ++- scripts/system/interstitialPage.js | 30 ++++++++++++++++-------------- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 73e2fce956..15416b26cb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3488,14 +3488,11 @@ bool Application::isServerlessMode() const { } bool Application::isInterstitialMode() const { - bool interstitialModeEnabled = Menu::getInstance()->isOptionChecked("Enable Interstitial"); - return interstitialModeEnabled ? _interstitialMode : false; + return _interstitialMode; } void Application::setIsInterstitialMode(bool interstitialMode) { - auto menu = Menu::getInstance(); - bool interstitialModeEnabled = menu->isOptionChecked("Enable Interstitial"); - if (_interstitialMode != interstitialMode && interstitialModeEnabled) { + if (_interstitialMode != interstitialMode) { _interstitialMode = interstitialMode; emit interstitialModeChanged(_interstitialMode); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 15898a73b1..a6ba983ab5 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -787,7 +787,6 @@ Menu::Menu() { // Developer > Show Overlays addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Overlays, 0, true); - addCheckableActionToQMenuAndActionHash(developerMenu, "Enable Interstitial", 0, false); #if 0 /// -------------- REMOVED FOR NOW -------------- addDisabledActionAndSeparator(navigateMenu, "History"); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index c333a0c8c2..31510831c8 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -34,7 +34,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/emote.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ - "system/controllers/controllerScripts.js" + "system/controllers/controllerScripts.js", + "system/interstitialPage.js" //"system/chat.js" ]; diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index c95845656c..aa84fed476 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -14,8 +14,9 @@ (function() { Script.include("/~/system/libraries/Xform.js"); - var DEBUG = false; - var MAX_X_SIZE = 3.8; + var DEBUG = true; + var MIN_LOADING_PROGRESS = 3.6; + var TOTAL_LOADING_PROGRESS = 3.8; var EPSILON = 0.01; var isVisible = false; var VOLUME = 0.4; @@ -55,7 +56,7 @@ "Tip: Log in to make friends, visit new domains, and save avatars!" ]; - var DEFAULT_DIMENSIONS = { x: 20, y: 20, z: 20 }; + var DEFAULT_DIMENSIONS = { x: 24, y: 24, z: 24 }; var loadingSphereID = Overlays.addOverlay("model", { name: "Loading-Sphere", @@ -87,10 +88,10 @@ var domainName = ""; var domainNameTextID = Overlays.addOverlay("text3d", { name: "Loading-Destination-Card-Text", - localPosition: { x: 0.0, y: 0.8, z: 0.0 }, + localPosition: { x: 0.0, y: 0.8, z: -0.001 }, text: domainName, textAlpha: 1, - backgroundAlpha: 0, + backgroundAlpha: 1, lineHeight: 0.42, visible: isVisible, ignoreRayIntersection: true, @@ -106,9 +107,10 @@ localPosition: { x: 0.0, y: 0.32, z: 0.0 }, text: domainText, textAlpha: 1, - backgroundAlpha: 0, + backgroundAlpha: 1, lineHeight: 0.13, visible: isVisible, + backgroundAlpha: 0, ignoreRayIntersection: true, drawInFront: true, grabbable: false, @@ -123,7 +125,7 @@ localPosition: { x: 0.0 , y: -1.6, z: 0.0 }, text: toolTip, textAlpha: 1, - backgroundAlpha: 0, + backgroundAlpha: 1, lineHeight: 0.13, visible: isVisible, ignoreRayIntersection: true, @@ -248,7 +250,6 @@ } function domainChanged(domain) { - print("domain changed: " + domain); if (domain !== currentDomain) { MyAvatar.restoreAnimation(); var name = location.placename; @@ -272,7 +273,6 @@ if (data.status === "success") { var domainInfo = data.data; var domainDescriptionText = domainInfo.place.description; - print("domainText: " + domainDescriptionText); var leftMargin = getLeftMargin(domainDescription, domainDescriptionText); var domainDescriptionProperties = { text: domainDescriptionText, @@ -381,18 +381,18 @@ var domainLoadingProgressPercentage = Window.domainLoadingProgress(); - var progress = MAX_X_SIZE * domainLoadingProgressPercentage; + var progress = MIN_LOADING_PROGRESS * domainLoadingProgressPercentage; if (progress >= target) { target = progress; } - if ((physicsEnabled && (currentProgress < MAX_X_SIZE))) { - target = MAX_X_SIZE; + if ((physicsEnabled && (currentProgress < TOTAL_LOADING_PROGRESS))) { + target = TOTAL_LOADING_PROGRESS; } currentProgress = lerp(currentProgress, target, 0.2); var properties = { - localPosition: { x: (1.85 - (currentProgress / 2) - (-0.029 * (currentProgress / MAX_X_SIZE))), y: -0.935, z: 0.0 }, + localPosition: { x: (1.85 - (currentProgress / 2) - (-0.029 * (currentProgress / TOTAL_LOADING_PROGRESS))), y: -0.935, z: 0.0 }, dimensions: { x: currentProgress, y: 2.8 @@ -400,9 +400,10 @@ }; Overlays.editOverlay(loadingBarProgress, properties); - if ((physicsEnabled && (currentProgress >= (MAX_X_SIZE - EPSILON)))) { + if ((physicsEnabled && (currentProgress >= (TOTAL_LOADING_PROGRESS - EPSILON)))) { updateOverlays((physicsEnabled || connectionToDomainFailed)); endAudio(); + currentDomain = "no domain"; timer = null; return; } @@ -452,6 +453,7 @@ renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = true; renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = true; renderViewTask.getConfig("LightingModel")["enablePointLight"] = true; + Menu.setIsOptionChecked("Show Overlays", physicsEnabled); if (!HMD.active) { toolbar.writeProperty("visible", true); } From 22df1b3bc699c1917a5b43530e4ab60b5a06d604 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 6 Sep 2018 17:00:12 -0700 Subject: [PATCH 344/744] Add comment to Assets.sendFakeHandshake about excluding jsdoc --- libraries/script-engine/src/AssetScriptingInterface.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index 72d6901fb5..0e05a563b2 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -108,6 +108,10 @@ public: Q_INVOKABLE void setBakingEnabled(QString path, bool enabled, QScriptValue callback); #if (PR_BUILD || DEV_BUILD) + /** + * This function is purely for development purposes, and not meant for use in a + * production context. It is not a public-facing API, so it should not contain jsdoc. + */ Q_INVOKABLE void sendFakedHandshake(); #endif From e76ebf7a0b8a4207436c04d7a30aef1d14919b1b Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 6 Sep 2018 17:29:16 -0700 Subject: [PATCH 345/744] changing location of shortcuts --- .../LoginDialog/+android/LinkAccountBody.qml | 40 ++++----- .../qml/LoginDialog/LinkAccountBody.qml | 87 ++++++++++--------- 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml index bf7807c85d..96b638c911 100644 --- a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml @@ -117,27 +117,27 @@ Item { } spacing: hifi.dimensions.contentSpacing.y / 2 - TextField { - id: usernameField - anchors { - horizontalCenter: parent.horizontalCenter - } - width: 1080 - placeholderText: qsTr("Username or Email") + TextField { + id: usernameField + anchors { + horizontalCenter: parent.horizontalCenter } + width: 1080 + placeholderText: qsTr("Username or Email") + } - TextField { - id: passwordField - anchors { - horizontalCenter: parent.horizontalCenter - } - width: 1080 - - placeholderText: qsTr("Password") - echoMode: TextInput.Password - - Keys.onReturnPressed: linkAccountBody.login() + TextField { + id: passwordField + anchors { + horizontalCenter: parent.horizontalCenter } + width: 1080 + + placeholderText: qsTr("Password") + echoMode: TextInput.Password + + Keys.onReturnPressed: linkAccountBody.login() + } } InfoItem { @@ -176,7 +176,7 @@ Item { anchors { left: parent.left top: form.bottom - topMargin: hifi.dimensions.contentSpacing.y / 2 + topMargin: hifi.dimensions.contentSpacing.y / 2 } spacing: hifi.dimensions.contentSpacing.x @@ -201,7 +201,7 @@ Item { anchors { right: parent.right top: form.bottom - topMargin: hifi.dimensions.contentSpacing.y / 2 + topMargin: hifi.dimensions.contentSpacing.y / 2 } spacing: hifi.dimensions.contentSpacing.x onHeightChanged: d.resize(); onWidthChanged: d.resize(); diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 902466270f..2274d88e04 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -15,13 +15,14 @@ import QtQuick.Controls.Styles 1.4 as OriginalStyles import "../controls-uit" import "../styles-uit" - Item { id: linkAccountBody clip: true height: root.pane.height width: root.pane.width property bool failAfterSignUp: false + property var locale: Qt.locale() + property string dateTimeString function login() { mainTextContainer.visible = false @@ -124,29 +125,27 @@ Item { placeholderText: "Username or Email" activeFocusOnPress: true + ShortcutText { + z: 10 + y: usernameField.height + anchors { + right: usernameField.right + topMargin: -19 + } + + text: "Forgot Username?" + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + linkColor: hifi.colors.blueAccent + + onLinkActivated: loginDialog.openUrl(link) + } + onFocusChanged: { root.text = ""; } } - ShortcutText { - id: forgotUsernameShortcut - z: 10 - anchors { - leftMargin: usernameField.textFieldLabel.contentWidth + 10 - topMargin: -19 - } - - text: "Forgot Username?" - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignRight - linkColor: hifi.colors.blueAccent - - onLinkActivated: loginDialog.openUrl(link) - Component.onCompleted: { - forgotUsernameShortcut.x = root.implicitWidth - forgotUsernameShortcut.width; - } - } TextField { id: passwordField @@ -154,11 +153,30 @@ Item { placeholderText: "Password" activeFocusOnPress: true echoMode: TextInput.Password + onHeightChanged: d.resize(); onWidthChanged: d.resize(); + + ShortcutText { + id: forgotPasswordShortcut + y: passwordField.height + z: 10 + anchors { + right: passwordField.right + } + + text: "Forgot Password?" + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + linkColor: hifi.colors.blueAccent + + onLinkActivated: loginDialog.openUrl(link) + } onFocusChanged: { root.text = ""; root.isPassword = true; } + Image { id: showPasswordImage x: parent.width - ((parent.height) * 31 / 23) @@ -190,7 +208,6 @@ Item { showPasswordImage.y = showPassword ? 0 : (passwordField.height - showPasswordImage.height) / 2; showPasswordHitbox.width = showPasswordImage.width; showPasswordHitbox.x = showPasswordImage.x; - } } } @@ -198,26 +215,6 @@ Item { Keys.onReturnPressed: linkAccountBody.login() } - ShortcutText { - id: forgotPasswordShortcut - z: 10 - anchors { - leftMargin: passwordField.textFieldLabel.contentWidth + 10 - topMargin: -19 - } - - text: "Forgot Password?" - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - linkColor: hifi.colors.blueAccent - - onLinkActivated: loginDialog.openUrl(link) - Component.onCompleted: { - forgotPasswordShortcut.x = root.implicitWidth - forgotPasswordShortcut.width; - } - } - InfoItem { id: additionalInformation @@ -304,6 +301,14 @@ Item { } usernameField.forceActiveFocus(); + + var data = { + "date": new Date().toLocaleString(), + }; + print(new Date().toLocaleString()); + print(model.sessionId); + + //UserActivityLogger.logAction("login_screen_shown", ) } Connections { From 7a9480723e61f58065acaeaaccdd72ec1b4ef393 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 6 Sep 2018 17:30:29 -0700 Subject: [PATCH 346/744] Remove unused variable in PickScriptingInterface::createTransformNode --- interface/src/raypick/PickScriptingInterface.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 41669c9f0c..0273b084b2 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -383,10 +383,6 @@ std::shared_ptr PickScriptingInterface::createTransformNode(const if (propMap["parentJointIndex"].isValid()) { parentJointIndex = propMap["parentJointIndex"].toInt(); } - glm::vec3 baseScale = glm::vec3(1); - if (propMap["baseScale"].isValid()) { - baseScale = vec3FromVariant(propMap["baseScale"]); - } auto sharedNestablePointer = nestablePointer.lock(); if (success && sharedNestablePointer) { NestableType nestableType = sharedNestablePointer->getNestableType(); From f40f40e135c8fbe205e9e7a8476304f94c086f30 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 6 Sep 2018 17:35:12 -0700 Subject: [PATCH 347/744] Use more reasonable value for BaseNestableTransformNode::_baseScale when given bad value --- libraries/shared/src/NestableTransformNode.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/shared/src/NestableTransformNode.h b/libraries/shared/src/NestableTransformNode.h index 21efd1c690..e73360d872 100644 --- a/libraries/shared/src/NestableTransformNode.h +++ b/libraries/shared/src/NestableTransformNode.h @@ -21,9 +21,7 @@ public: auto nestablePointer = _spatiallyNestable.lock(); if (nestablePointer) { glm::vec3 nestableDimensions = getActualScale(nestablePointer); - if (!glm::any(glm::equal(nestableDimensions, glm::vec3(0.0f)))) { - _baseScale = nestableDimensions; - } + _baseScale = glm::max(glm::vec3(0.001f), nestableDimensions); } } From 52bc09cd2aa29071d4fa56fb792fcc454be81d29 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 6 Sep 2018 17:44:06 -0700 Subject: [PATCH 348/744] Add override qualifier to BaseNestableTransformNode::getTransform --- libraries/shared/src/NestableTransformNode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/NestableTransformNode.h b/libraries/shared/src/NestableTransformNode.h index e73360d872..2f9bc2e985 100644 --- a/libraries/shared/src/NestableTransformNode.h +++ b/libraries/shared/src/NestableTransformNode.h @@ -25,7 +25,7 @@ public: } } - Transform getTransform() { + Transform getTransform() override { std::shared_ptr nestable = _spatiallyNestable.lock(); if (!nestable) { return Transform(); From 45a99a9244679da6d30ba489b6a225a0be4ad1d4 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 6 Sep 2018 17:55:10 -0700 Subject: [PATCH 349/744] fixing some bugs --- scripts/system/interstitialPage.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index aa84fed476..1cc048ca99 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -14,7 +14,7 @@ (function() { Script.include("/~/system/libraries/Xform.js"); - var DEBUG = true; + var DEBUG = false; var MIN_LOADING_PROGRESS = 3.6; var TOTAL_LOADING_PROGRESS = 3.8; var EPSILON = 0.01; @@ -410,8 +410,21 @@ timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); } - + var whiteColor = {red: 255, green: 255, blue: 255}; + var greyColor = {red: 125, green: 125, blue: 125}; Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); + Overlays.hoverEnterOverlay.connect(function(overlayID, event) { + if (overlayID === loadingToTheSpotID) { + Overlays.editOverlay(loadingToTheSpotID, { color: greyColor}); + } + }); + + Overlays.hoverLeaveOverlay.connect(function(overlayID, event) { + if (overlayID === loadingToTheSpotID) { + Overlays.editOverlay(loadingToTheSpotID, { color: whiteColor}); + } + }); + location.hostChanged.connect(domainChanged); location.lookupResultsFinished.connect(function() { Script.setTimeout(function() { @@ -453,7 +466,7 @@ renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = true; renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = true; renderViewTask.getConfig("LightingModel")["enablePointLight"] = true; - Menu.setIsOptionChecked("Show Overlays", physicsEnabled); + Menu.setIsOptionChecked("Show Overlays", true); if (!HMD.active) { toolbar.writeProperty("visible", true); } From 6508533a15fd3035593535ae87bdb6316b8d6eb1 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 6 Sep 2018 17:58:40 -0700 Subject: [PATCH 350/744] Remove anim stats from plain ole stats. There are still present in anim stats tho. --- interface/resources/qml/Stats.qml | 83 -------------------------- interface/src/ui/Stats.cpp | 98 ------------------------------- interface/src/ui/Stats.h | 25 +------- 3 files changed, 1 insertion(+), 205 deletions(-) diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index f74f0539c9..1a29ce87df 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -192,89 +192,6 @@ Item { StatText { text: "Yaw: " + root.yaw.toFixed(1) } - StatText { - visible: root.animStateMachines.length > 0; - text: "Anim State Machines:" - } - ListView { - width: geoCol.width - height: root.animStateMachines.length * 15 - visible: root.animStateMchines.length > 0; - model: root.animStateMachines - delegate: StatText { - text: { - if (modelData.length > 30) { - return modelData.substring(0, 5) + "..." + modelData.substring(.length - 22); - } else { - return modelData; - } - } - } - } - StatText { - visible: root.animAlphaValues.length > 0; - text: "Anim Alpha Values:" - } - ListView { - width: geoCol.width - height: root.animAlphaValues.length * 15 - visible: root.animAlphaValues.length > 0; - model: root.animAlphaValues - delegate: StatText { - text: { - var actualText = modelData.split("|")[1]; - if (actualText) { - if (actualText.length > 30) { - return actualText.substring(0, 5) + "..." + actualText.substring(actualText.length - 22); - } else { - return actualText; - } - } else { - return modelData; - } - } - color: { - var grayScale = parseFloat(modelData.split("|")[0]); - return Qt.rgba(1.0, 1.0, 1.0, grayScale); - } - styleColor: { - var grayScale = parseFloat(modelData.split("|")[0]); - return Qt.rgba(0.0, 0.0, 0.0, grayScale); - } - } - } - StatText { - visible: root.animVars.length > 0; - text: "AnimVars:" - } - ListView { - width: geoCol.width - height: root.animVars.length * 15 - visible: root.animVars.length > 0; - model: root.animVars - delegate: StatText { - text: { - var actualText = modelData.split("|")[1]; - if (actualText) { - if (actualText.length > 30) { - return actualText.substring(0, 5) + "..." + actualText.substring(actualText.length - 22); - } else { - return actualText; - } - } else { - return modelData; - } - } - color: { - var grayScale = parseFloat(modelData.split("|")[0]); - return Qt.rgba(1.0, 1.0, 1.0, grayScale); - } - styleColor: { - var grayScale = parseFloat(modelData.split("|")[0]); - return Qt.rgba(0.0, 0.0, 0.0, grayScale); - } - } - } StatText { visible: root.expanded; text: "Avatar Mixer In: " + root.avatarMixerInKbps + " kbps, " + diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index bfb83f10f8..d51b90f15d 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -207,103 +207,6 @@ void Stats::updateStats(bool force) { // Third column, avatar stats auto myAvatar = avatarManager->getMyAvatar(); - auto debugAlphaMap = myAvatar->getSkeletonModel()->getRig().getDebugAlphaMap(); - - // update animation debug alpha values - QStringList newAnimAlphaValues; - qint64 now = usecTimestampNow(); - for (auto& iter : debugAlphaMap) { - QString key = iter.first; - float alpha = std::get<0>(iter.second); - - auto prevIter = _prevDebugAlphaMap.find(key); - if (prevIter != _prevDebugAlphaMap.end()) { - float prevAlpha = std::get<0>(iter.second); - if (prevAlpha != alpha) { - // change detected: reset timer - _animAlphaValueChangedTimers[key] = now; - } - } else { - // new value: start timer - _animAlphaValueChangedTimers[key] = now; - } - - AnimNodeType type = std::get<1>(iter.second); - if (type == AnimNodeType::Clip) { - - // figure out the grayScale color of this line. - const float LIT_TIME = 2.0f; - const float FADE_OUT_TIME = 1.0f; - float grayScale = 0.0f; - float secondsElapsed = (float)(now - _animAlphaValueChangedTimers[key]) / (float)USECS_PER_SECOND; - if (secondsElapsed < LIT_TIME) { - grayScale = 1.0f; - } else if (secondsElapsed < LIT_TIME + FADE_OUT_TIME) { - grayScale = (FADE_OUT_TIME - (secondsElapsed - LIT_TIME)) / FADE_OUT_TIME; - } else { - grayScale = 0.0f; - } - - if (grayScale > 0.0f) { - // append grayScaleColor to start of debug string - newAnimAlphaValues << QString::number(grayScale, 'f', 2) + "|" + key + ": " + QString::number(alpha, 'f', 3); - } - } - } - - _animAlphaValues = newAnimAlphaValues; - _prevDebugAlphaMap = debugAlphaMap; - - emit animAlphaValuesChanged(); - - // update animation anim vars - _animVarsList.clear(); - auto animVars = myAvatar->getSkeletonModel()->getRig().getAnimVars().toDebugMap(); - for (auto& iter : animVars) { - QString key = iter.first; - QString value = iter.second; - - auto prevIter = _prevAnimVars.find(key); - if (prevIter != _prevAnimVars.end()) { - QString prevValue = prevIter->second; - if (value != prevValue) { - // change detected: reset timer - _animVarChangedTimers[key] = now; - } - } else { - // new value: start timer - _animVarChangedTimers[key] = now; - } - - // figure out the grayScale color of this line. - const float LIT_TIME = 2.0f; - const float FADE_OUT_TIME = 0.5f; - float grayScale = 0.0f; - float secondsElapsed = (float)(now - _animVarChangedTimers[key]) / (float)USECS_PER_SECOND; - if (secondsElapsed < LIT_TIME) { - grayScale = 1.0f; - } else if (secondsElapsed < LIT_TIME + FADE_OUT_TIME) { - grayScale = (FADE_OUT_TIME - (secondsElapsed - LIT_TIME)) / FADE_OUT_TIME; - } else { - grayScale = 0.0f; - } - - if (grayScale > 0.0f) { - // append grayScaleColor to start of debug string - _animVarsList << QString::number(grayScale, 'f', 2) + "|" + key + ": " + value; - } - } - _prevAnimVars = animVars; - emit animVarsChanged(); - - // animation state machines - _animStateMachines.clear(); - auto stateMachineMap = myAvatar->getSkeletonModel()->getRig().getStateMachineMap(); - for (auto& iter : stateMachineMap) { - _animStateMachines << iter.second; - } - emit animStateMachinesChanged(); - glm::vec3 avatarPos = myAvatar->getWorldPosition(); STAT_UPDATE(position, QVector3D(avatarPos.x, avatarPos.y, avatarPos.z)); STAT_UPDATE_FLOAT(speed, glm::length(myAvatar->getWorldVelocity()), 0.01f); @@ -436,7 +339,6 @@ void Stats::updateStats(bool force) { } sendingModeStream << "] " << serverCount << " servers"; if (movingServerCount > 0) { - sendingModeStream << " "; } else { sendingModeStream << " "; diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 1febfff8b5..ae608cfddb 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -14,7 +14,6 @@ #include #include #include -#include #define STATS_PROPERTY(type, name, initialValue) \ Q_PROPERTY(type name READ name NOTIFY name##Changed) \ @@ -184,7 +183,6 @@ class Stats : public QQuickItem { HIFI_QML_DECL Q_PROPERTY(bool expanded READ isExpanded WRITE setExpanded NOTIFY expandedChanged) Q_PROPERTY(bool timingExpanded READ isTimingExpanded NOTIFY timingExpandedChanged) - Q_PROPERTY(QString monospaceFont READ monospaceFont CONSTANT) STATS_PROPERTY(int, serverCount, 0) @@ -293,9 +291,6 @@ class Stats : public QQuickItem { STATS_PROPERTY(float, batchFrameTime, 0) STATS_PROPERTY(float, engineFrameTime, 0) STATS_PROPERTY(float, avatarSimulationTime, 0) - Q_PROPERTY(QStringList animAlphaValues READ animAlphaValues NOTIFY animAlphaValuesChanged) - Q_PROPERTY(QStringList animVars READ animVars NOTIFY animVarsChanged) - Q_PROPERTY(QStringList animStateMachines READ animStateMachines NOTIFY animStateMachinesChanged) STATS_PROPERTY(int, stylusPicksCount, 0) STATS_PROPERTY(int, rayPicksCount, 0) @@ -329,9 +324,6 @@ public: } QStringList downloadUrls () { return _downloadUrls; } - QStringList animAlphaValues() { return _animAlphaValues; } - QStringList animVars() { return _animVarsList; } - QStringList animStateMachines() { return _animStateMachines; } public slots: void forceUpdateStats() { updateStats(true); } @@ -1033,10 +1025,6 @@ signals: */ void avatarSimulationTimeChanged(); - void animAlphaValuesChanged(); - void animVarsChanged(); - void animStateMachinesChanged(); - /**jsdoc * Triggered when the value of the rectifiedTextureCount property changes. * @function Stats.rectifiedTextureCountChanged @@ -1051,7 +1039,6 @@ signals: */ void decimatedTextureCountChanged(); - // QQuickItem signals. /**jsdoc @@ -1337,17 +1324,7 @@ private: bool _showGameUpdateStats{ false }; QString _monospaceFont; const AudioIOStats* _audioStats; - QStringList _downloadUrls; - - QStringList _animAlphaValues; - AnimContext::DebugAlphaMap _prevDebugAlphaMap; // alpha values from previous frame - std::map _animAlphaValueChangedTimers; // last time alpha value has changed - - QStringList _animVarsList; - std::map _prevAnimVars; // anim vars from previous frame - std::map _animVarChangedTimers; // last time animVar value has changed. - - QStringList _animStateMachines; + QStringList _downloadUrls = QStringList(); }; #endif // hifi_Stats_h From 22cfad248310788459d594c874d7c5fee1bce902 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 6 Sep 2018 18:32:37 -0700 Subject: [PATCH 351/744] Squelch (false) warning from gcc --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 705d660a2e..784205a2da 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -409,7 +409,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map - AvatarData::AvatarDataDetail detail; + AvatarData::AvatarDataDetail detail = AvatarData::NoData; // NOTE: Here's where we determine if we are over budget and drop to bare minimum data int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; From d0e73b00f783bb66a5083a146c8ec427378630a5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 7 Sep 2018 15:41:30 +1200 Subject: [PATCH 352/744] Restructure mini tablet code --- scripts/system/miniTablet.js | 1272 +++++++++++++++++++--------------- 1 file changed, 696 insertions(+), 576 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 52ca5c947a..c53fab88a5 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -16,118 +16,20 @@ Script.include("./libraries/utils.js"); - var // Base overlay - proxyOverlay = null, - PROXY_MODEL = Script.resolvePath("./assets/models/tinyTablet.fbx"), - PROXY_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0045 }, // Proportional to tablet proper. - PROXY_POSITIONS = [ - { - x: -0.03, // Distance across hand. - y: 0.08, // Distance from joint. - z: 0.06 // Distance above palm. - }, - { - x: 0.03, // Distance across hand. - y: 0.08, // Distance from joint. - z: 0.06 // Distance above palm. - } - ], - PROXY_ROTATIONS = [ - Quat.fromVec3Degrees({ x: 0, y: 180 - 40, z: 90 }), - Quat.fromVec3Degrees({ x: 0, y: 180 + 40, z: -90 }), - ], + var UI, + ui = null, + State, + miniState = null, - // UI overlay. - proxyUIOverlay = null, - PROXY_UI_HTML = Script.resolvePath("./html/miniTablet.html"), - PROXY_UI_DIMENSIONS = { x: 0.059, y: 0.0865 }, - PROXY_UI_WIDTH_PIXELS = 150, - METERS_TO_INCHES = 39.3701, - PROXY_UI_DPI = PROXY_UI_WIDTH_PIXELS / (PROXY_UI_DIMENSIONS.x * METERS_TO_INCHES), - PROXY_UI_OFFSET = 0.001, // Above model surface. - PROXY_UI_LOCAL_POSITION = { x: 0.0002, y: 0.0024, z: -(PROXY_DIMENSIONS.z / 2 + PROXY_UI_OFFSET) }, - PROXY_UI_LOCAL_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), - proxyUIOverlayEnabled = false, - PROXY_UI_OVERLAY_ENABLED_DELAY = 500, - proxyOverlayObject = null, - - MUTE_ON_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-mute-a.svg", - MUTE_OFF_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-unmute-i.svg", - BUBBLE_ON_ICON = Script.resourcesPath() + "icons/tablet-icons/bubble-a.svg", - BUBBLE_OFF_ICON = Script.resourcesPath() + "icons/tablet-icons/bubble-i.svg", - - // State machine - PROXY_DISABLED = 0, - PROXY_HIDDEN = 1, - PROXY_HIDING = 2, - PROXY_SHOWING = 3, - PROXY_VISIBLE = 4, - PROXY_GRABBED = 5, - PROXY_EXPANDING = 6, - TABLET_OPEN = 7, - STATE_STRINGS = ["PROXY_DISABLED", "PROXY_HIDDEN", "PROXY_HIDING", "PROXY_SHOWING", "PROXY_VISIBLE", "PROXY_GRABBED", - "PROXY_EXPANDING", "TABLET_OPEN"], - STATE_MACHINE, - rezzerState = PROXY_DISABLED, - proxyHand, - PROXY_SCALE_DURATION = 150, - PROXY_SCALE_TIMEOUT = 20, - proxyScaleTimer = null, - proxyScaleStart, - PROXY_EXPAND_HANDLES = [ // Normalized coordinates in range [-0.5, 0.5] about center of mini tablet. - { x: 0.5, y: -0.65, z: 0 }, - { x: -0.5, y: -0.65, z: 0 } - ], - PROXY_EXPAND_DELTA_ROTATION = Quat.fromVec3Degrees({ x: -5, y: 0, z: 0 }), - PROXY_EXPAND_HANDLES_OTHER = [ // Different handles when expanding after being grabbed by other hand, - { x: 0.5, y: -0.4, z: 0 }, - { x: -0.5, y: -0.4, z: 0 } - ], - PROXY_EXPAND_DELTA_ROTATION_OTHER = Quat.IDENTITY, - proxyExpandHand, - proxyExpandHandles = PROXY_EXPAND_HANDLES, - proxyExpandDeltaRotation = PROXY_EXPAND_HANDLES_OTHER, - proxyExpandLocalPosition, - proxyExpandLocalRotation = Quat.IDENTITY, - PROXY_EXPAND_DURATION = 250, - PROXY_EXPAND_TIMEOUT = 20, - proxyExpandTimer = null, - proxyExpandStart, - proxyInitialWidth, - proxyTargetWidth, - proxyTargetLocalRotation, - - // EventBridge - READY_MESSAGE = "ready", // Engine <== Dialog - HOVER_MESSAGE = "hover", // Engine <== Dialog - MUTE_MESSAGE = "mute", // Engine <=> Dialog - BUBBLE_MESSAGE = "bubble", // Engine <=> Dialog - EXPAND_MESSAGE = "expand", // Engine <== Dialog - - // Events - MIN_HAND_CAMERA_ANGLE = 30, - DEGREES_180 = 180, - MIN_HAND_CAMERA_ANGLE_COS = Math.cos(Math.PI * MIN_HAND_CAMERA_ANGLE / DEGREES_180), - updateTimer = null, - UPDATE_INTERVAL = 300, - HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation", - avatarScale = 1, - - // Sounds - HOVER_SOUND = "./assets/sounds/button-hover.wav", - HOVER_VOLUME = 0.5, - CLICK_SOUND = "./assets/sounds/button-click.wav", - CLICK_VOLUME = 0.8, - hoverSound = SoundCache.getSound(Script.resolvePath(HOVER_SOUND)), - clickSound = SoundCache.getSound(Script.resolvePath(CLICK_SOUND)), - - // Hands + // Hands. LEFT_HAND = 0, RIGHT_HAND = 1, HAND_NAMES = ["LeftHand", "RightHand"], // Miscellaneous. + HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation", tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"), + avatarScale = 1, // Sanitized MyAvatar.scale. DEBUG = false; // #region Utilities ======================================================================================================= @@ -173,498 +75,713 @@ return hand === LEFT_HAND ? RIGHT_HAND : LEFT_HAND; } - function playSound(sound, volume) { - Audio.playSound(sound, { - position: proxyHand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(), - volume: volume, - localOnly: true - }); - } - - // #endregion - - // #region Communications ================================================================================================== - - function updateMiniTabletID() { - // Send mini-tablet overlay ID to controllerDispatcher so that it can use a smaller near grab distance. - Messages.sendLocalMessage("Hifi-MiniTablet-ID", proxyOverlay); - // Send mini-tablet UI overlay ID to stylusInput so that it styluses can be used on it. - Messages.sendLocalMessage("Hifi-MiniTablet-UI-ID", proxyUIOverlay); - } - - function updateMutedStatus() { - var isMuted = Audio.muted; - proxyOverlayObject.emitScriptEvent(JSON.stringify({ - type: MUTE_MESSAGE, - on: isMuted, - icon: isMuted ? MUTE_ON_ICON : MUTE_OFF_ICON - })); - } - - function updateBubbleStatus() { - var isBubbleOn = Users.getIgnoreRadiusEnabled(); - proxyOverlayObject.emitScriptEvent(JSON.stringify({ - type: BUBBLE_MESSAGE, - on: isBubbleOn, - icon: isBubbleOn ? BUBBLE_ON_ICON : BUBBLE_OFF_ICON - })); - } - - function onWebEventReceived(data) { - var message; - - try { - message = JSON.parse(data); - } catch (e) { - console.error("EventBridge message error"); - return; - } - - switch (message.type) { - case READY_MESSAGE: - // Send initial button statuses. - updateMutedStatus(); - updateBubbleStatus(); - break; - case HOVER_MESSAGE: - // Audio feedback. - playSound(hoverSound, HOVER_VOLUME); - break; - case MUTE_MESSAGE: - // Toggle mute. - playSound(clickSound, CLICK_VOLUME); - Audio.muted = !Audio.muted; - break; - case BUBBLE_MESSAGE: - // Toggle bubble. - playSound(clickSound, CLICK_VOLUME); - Users.toggleIgnoreRadius(); - break; - case EXPAND_MESSAGE: - // Expand tablet; - playSound(clickSound, CLICK_VOLUME); - setState(PROXY_EXPANDING, proxyHand); - break; - } - } - // #endregion // #region UI ============================================================================================================== - function createUI() { - proxyOverlay = Overlays.addOverlay("model", { - url: PROXY_MODEL, - dimensions: Vec3.multiply(avatarScale, PROXY_DIMENSIONS), - solid: true, - grabbable: true, - showKeyboardFocusHighlight: false, - displayInFront: true, - visible: false - }); - proxyUIOverlay = Overlays.addOverlay("web3d", { - url: PROXY_UI_HTML, - parentID: proxyOverlay, - localPosition: Vec3.multiply(avatarScale, PROXY_UI_LOCAL_POSITION), - localRotation: PROXY_UI_LOCAL_ROTATION, - dimensions: Vec3.multiply(avatarScale, PROXY_UI_DIMENSIONS), - dpi: PROXY_UI_DPI / avatarScale, - alpha: 0, // Hide overlay while its content is being created. - grabbable: false, - showKeyboardFocusHighlight: false, - displayInFront: true, - visible: false - }); + UI = function () { - proxyUIOverlayEnabled = false; // This and alpha = 0 hides overlay while its content is being created. - - proxyOverlayObject = Overlays.getOverlayObject(proxyUIOverlay); - proxyOverlayObject.webEventReceived.connect(onWebEventReceived); - - // updateMiniTabletID(); Other scripts relying on this may not be ready yet so do this in showUI(). - } - - function showUI() { - var initialScale = 0.01; // Start very small. - - Overlays.editOverlay(proxyOverlay, { - parentID: MyAvatar.SELF_ID, - parentJointIndex: handJointIndex(proxyHand), - localPosition: Vec3.multiply(avatarScale, PROXY_POSITIONS[proxyHand]), - localRotation: PROXY_ROTATIONS[proxyHand], - dimensions: Vec3.multiply(initialScale, PROXY_DIMENSIONS), - visible: true - }); - Overlays.editOverlay(proxyUIOverlay, { - localPosition: Vec3.multiply(avatarScale, PROXY_UI_LOCAL_POSITION), - localRotation: PROXY_UI_LOCAL_ROTATION, - dimensions: Vec3.multiply(initialScale, PROXY_UI_DIMENSIONS), - dpi: PROXY_UI_DPI / initialScale, - visible: true - }); - - updateMiniTabletID(); - - if (!proxyUIOverlayEnabled) { - // Overlay content is created the first time it is visible to the user. The initial creation displays artefacts. - // Delay showing UI overlay until after giving it time for its content to be created. - Script.setTimeout(function () { - Overlays.editOverlay(proxyUIOverlay, { alpha: 1.0 }); - }, PROXY_UI_OVERLAY_ENABLED_DELAY); + if (!(this instanceof UI)) { + return new UI(); } - } - function sizeUI(scaleFactor) { - // Scale UI in place. - Overlays.editOverlay(proxyOverlay, { - dimensions: Vec3.multiply(scaleFactor, PROXY_DIMENSIONS) - }); - Overlays.editOverlay(proxyUIOverlay, { - localPosition: Vec3.multiply(scaleFactor, PROXY_UI_LOCAL_POSITION), - dimensions: Vec3.multiply(scaleFactor, PROXY_UI_DIMENSIONS), - dpi: PROXY_UI_DPI / scaleFactor - }); - } + var // Base overlay + miniOverlay = null, + MINI_MODEL = Script.resolvePath("./assets/models/tinyTablet.fbx"), + MINI_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0045 }, // Proportional to tablet proper. + MINI_POSITIONS = [ + { + x: -0.03, // Distance across hand. + y: 0.08, // Distance from joint. + z: 0.06 // Distance above palm. + }, + { + x: 0.03, // Distance across hand. + y: 0.08, // Distance from joint. + z: 0.06 // Distance above palm. + } + ], + DEGREES_180 = 180, + MINI_PICTH = 40, + MINI_ROTATIONS = [ + Quat.fromVec3Degrees({ x: 0, y: DEGREES_180 - MINI_PICTH, z: 90 }), + Quat.fromVec3Degrees({ x: 0, y: DEGREES_180 + MINI_PICTH, z: -90 }) + ], - function sizeUIAboutHandles(scaleFactor) { - // Scale UI and move per handles. - var tabletScaleFactor = avatarScale * (1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth); - var dimensions = Vec3.multiply(tabletScaleFactor, PROXY_DIMENSIONS); - var localRotation = Quat.mix(proxyExpandLocalRotation, proxyTargetLocalRotation, scaleFactor); - var localPosition = - Vec3.sum(proxyExpandLocalPosition, - Vec3.multiplyQbyV(proxyExpandLocalRotation, - Vec3.multiply(-tabletScaleFactor, - Vec3.multiplyVbyV(proxyExpandHandles[proxyExpandHand], PROXY_DIMENSIONS))) - ); - localPosition = Vec3.sum(localPosition, - Vec3.multiplyQbyV(proxyExpandLocalRotation, { x: 0, y: 0.5 * -dimensions.y, z: 0 })); - localPosition = Vec3.sum(localPosition, - Vec3.multiplyQbyV(localRotation, { x: 0, y: 0.5 * dimensions.y, z: 0 })); - Overlays.editOverlay(proxyOverlay, { - localPosition: localPosition, - localRotation: localRotation, - dimensions: dimensions - }); - Overlays.editOverlay(proxyUIOverlay, { - localPosition: Vec3.multiply(tabletScaleFactor, PROXY_UI_LOCAL_POSITION), - dimensions: Vec3.multiply(tabletScaleFactor, PROXY_UI_DIMENSIONS), - dpi: PROXY_UI_DPI / tabletScaleFactor - }); - } + // UI overlay. + uiHand = LEFT_HAND, + miniUIOverlay = null, + MINI_UI_HTML = Script.resolvePath("./html/miniTablet.html"), + MINI_UI_DIMENSIONS = { x: 0.059, y: 0.0865 }, + MINI_UI_WIDTH_PIXELS = 150, + METERS_TO_INCHES = 39.3701, + MINI_UI_DPI = MINI_UI_WIDTH_PIXELS / (MINI_UI_DIMENSIONS.x * METERS_TO_INCHES), + MINI_UI_OFFSET = 0.001, // Above model surface. + MINI_UI_LOCAL_POSITION = { x: 0.0002, y: 0.0024, z: -(MINI_DIMENSIONS.z / 2 + MINI_UI_OFFSET) }, + MINI_UI_LOCAL_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + miniUIOverlayEnabled = false, + MINI_UI_OVERLAY_ENABLED_DELAY = 500, + miniOverlayObject = null, - function hideUI() { - Overlays.editOverlay(proxyOverlay, { - parentID: Uuid.NULL, // Release hold so that hand can grab tablet proper. - visible: false - }); - Overlays.editOverlay(proxyUIOverlay, { - visible: false - }); - } + // Button icons. + MUTE_ON_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-mute-a.svg", + MUTE_OFF_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-unmute-i.svg", + BUBBLE_ON_ICON = Script.resourcesPath() + "icons/tablet-icons/bubble-a.svg", + BUBBLE_OFF_ICON = Script.resourcesPath() + "icons/tablet-icons/bubble-i.svg", + + // Expansion to tablet. + MINI_EXPAND_HANDLES = [ // Normalized coordinates in range [-0.5, 0.5] about center of mini tablet. + { x: 0.5, y: -0.65, z: 0 }, + { x: -0.5, y: -0.65, z: 0 } + ], + MINI_EXPAND_DELTA_ROTATION = Quat.fromVec3Degrees({ x: -5, y: 0, z: 0 }), + MINI_EXPAND_HANDLES_OTHER = [ // Different handles when expanding after being grabbed by other hand, + { x: 0.5, y: -0.4, z: 0 }, + { x: -0.5, y: -0.4, z: 0 } + ], + MINI_EXPAND_DELTA_ROTATION_OTHER = Quat.IDENTITY, + miniExpandHand, + miniExpandHandles = MINI_EXPAND_HANDLES, + miniExpandDeltaRotation = MINI_EXPAND_HANDLES_OTHER, + miniExpandLocalPosition, + miniExpandLocalRotation = Quat.IDENTITY, + miniInitialWidth, + miniTargetWidth, + miniTargetLocalRotation, + + // EventBridge + READY_MESSAGE = "ready", // Engine <== Dialog + HOVER_MESSAGE = "hover", // Engine <== Dialog + MUTE_MESSAGE = "mute", // Engine <=> Dialog + BUBBLE_MESSAGE = "bubble", // Engine <=> Dialog + EXPAND_MESSAGE = "expand", // Engine <== Dialog + + // Sounds + HOVER_SOUND = "./assets/sounds/button-hover.wav", + HOVER_VOLUME = 0.5, + CLICK_SOUND = "./assets/sounds/button-click.wav", + CLICK_VOLUME = 0.8, + hoverSound = SoundCache.getSound(Script.resolvePath(HOVER_SOUND)), + clickSound = SoundCache.getSound(Script.resolvePath(CLICK_SOUND)); + + + function updateMutedStatus() { + var isMuted = Audio.muted; + miniOverlayObject.emitScriptEvent(JSON.stringify({ + type: MUTE_MESSAGE, + on: isMuted, + icon: isMuted ? MUTE_ON_ICON : MUTE_OFF_ICON + })); + } + + function updateBubbleStatus() { + var isBubbleOn = Users.getIgnoreRadiusEnabled(); + miniOverlayObject.emitScriptEvent(JSON.stringify({ + type: BUBBLE_MESSAGE, + on: isBubbleOn, + icon: isBubbleOn ? BUBBLE_ON_ICON : BUBBLE_OFF_ICON + })); + } + + function onWebEventReceived(data) { + var message; + + try { + message = JSON.parse(data); + } catch (e) { + console.error("EventBridge message error"); + return; + } + + switch (message.type) { + case READY_MESSAGE: + // Send initial button statuses. + updateMutedStatus(); + updateBubbleStatus(); + break; + case HOVER_MESSAGE: + // Audio feedback. + playSound(hoverSound, HOVER_VOLUME); + break; + case MUTE_MESSAGE: + // Toggle mute. + playSound(clickSound, CLICK_VOLUME); + Audio.muted = !Audio.muted; + break; + case BUBBLE_MESSAGE: + // Toggle bubble. + playSound(clickSound, CLICK_VOLUME); + Users.toggleIgnoreRadius(); + break; + case EXPAND_MESSAGE: + // Expand tablet; + playSound(clickSound, CLICK_VOLUME); + miniState.setState(miniState.MINI_EXPANDING, uiHand); + break; + } + } + + + function updateMiniTabletID() { + // Send mini-tablet overlay ID to controllerDispatcher so that it can use a smaller near grab distance. + Messages.sendLocalMessage("Hifi-MiniTablet-ID", miniOverlay); + // Send mini-tablet UI overlay ID to stylusInput so that it styluses can be used on it. + Messages.sendLocalMessage("Hifi-MiniTablet-UI-ID", miniUIOverlay); + } + + function playSound(sound, volume) { + Audio.playSound(sound, { + position: uiHand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(), + volume: volume, + localOnly: true + }); + } + + + function getUIPositionAndRotation(hand) { + return { + position: MINI_POSITIONS[hand], + rotation: MINI_ROTATIONS[hand] + }; + } + + function getMiniTabletID() { + return miniOverlay; + } + + function getMiniTabletProperties() { + var properties = Overlays.getProperties(miniOverlay, ["position", "orientation"]); + return { + position: properties.position, + orientation: properties.orientation + }; + } + + + function show(hand) { + var initialScale = 0.01; // Start very small. + + uiHand = hand; + + Overlays.editOverlay(miniOverlay, { + parentID: MyAvatar.SELF_ID, + parentJointIndex: handJointIndex(hand), + localPosition: Vec3.multiply(avatarScale, MINI_POSITIONS[hand]), + localRotation: MINI_ROTATIONS[hand], + dimensions: Vec3.multiply(initialScale, MINI_DIMENSIONS), + visible: true + }); + Overlays.editOverlay(miniUIOverlay, { + localPosition: Vec3.multiply(avatarScale, MINI_UI_LOCAL_POSITION), + localRotation: MINI_UI_LOCAL_ROTATION, + dimensions: Vec3.multiply(initialScale, MINI_UI_DIMENSIONS), + dpi: MINI_UI_DPI / initialScale, + visible: true + }); - function destroyUI() { - if (proxyOverlayObject) { - proxyOverlayObject.webEventReceived.disconnect(onWebEventReceived); - Overlays.deleteOverlay(proxyUIOverlay); - Overlays.deleteOverlay(proxyOverlay); - proxyOverlayObject = null; - proxyUIOverlay = null; - proxyOverlay = null; updateMiniTabletID(); + + if (!miniUIOverlayEnabled) { + // Overlay content is created the first time it is visible to the user. The initial creation displays artefacts. + // Delay showing UI overlay until after giving it time for its content to be created. + Script.setTimeout(function () { + Overlays.editOverlay(miniUIOverlay, { alpha: 1.0 }); + }, MINI_UI_OVERLAY_ENABLED_DELAY); + } } - } + + function size(scaleFactor) { + // Scale UI in place. + Overlays.editOverlay(miniOverlay, { + dimensions: Vec3.multiply(scaleFactor, MINI_DIMENSIONS) + }); + Overlays.editOverlay(miniUIOverlay, { + localPosition: Vec3.multiply(scaleFactor, MINI_UI_LOCAL_POSITION), + dimensions: Vec3.multiply(scaleFactor, MINI_UI_DIMENSIONS), + dpi: MINI_UI_DPI / scaleFactor + }); + } + + function startExpandingTablet(hand) { + // Expansion details. + if (hand === uiHand) { + miniExpandHandles = MINI_EXPAND_HANDLES; + miniExpandDeltaRotation = MINI_EXPAND_DELTA_ROTATION; + } else { + miniExpandHandles = MINI_EXPAND_HANDLES_OTHER; + miniExpandDeltaRotation = MINI_EXPAND_DELTA_ROTATION_OTHER; + } + + // Grab details. + var properties = Overlays.getProperties(miniOverlay, ["localPosition", "localRotation"]); + miniExpandHand = hand; + miniExpandLocalRotation = properties.localRotation; + miniExpandLocalPosition = Vec3.sum(properties.localPosition, + Vec3.multiplyQbyV(miniExpandLocalRotation, + Vec3.multiplyVbyV(miniExpandHandles[miniExpandHand], MINI_DIMENSIONS))); + + // Start expanding. + miniInitialWidth = MINI_DIMENSIONS.x; + miniTargetWidth = getTabletWidthFromSettings(); + miniTargetLocalRotation = Quat.multiply(miniExpandLocalRotation, miniExpandDeltaRotation); + } + + function sizeAboutHandles(scaleFactor) { + // Scale UI and move per handles. + var tabletScaleFactor, + dimensions, + localRotation, + localPosition; + + tabletScaleFactor = avatarScale * (1 + scaleFactor * (miniTargetWidth - miniInitialWidth) / miniInitialWidth); + dimensions = Vec3.multiply(tabletScaleFactor, MINI_DIMENSIONS); + localRotation = Quat.mix(miniExpandLocalRotation, miniTargetLocalRotation, scaleFactor); + localPosition = + Vec3.sum(miniExpandLocalPosition, + Vec3.multiplyQbyV(miniExpandLocalRotation, + Vec3.multiply(-tabletScaleFactor, + Vec3.multiplyVbyV(miniExpandHandles[miniExpandHand], MINI_DIMENSIONS))) + ); + localPosition = Vec3.sum(localPosition, + Vec3.multiplyQbyV(miniExpandLocalRotation, { x: 0, y: 0.5 * -dimensions.y, z: 0 })); + localPosition = Vec3.sum(localPosition, + Vec3.multiplyQbyV(localRotation, { x: 0, y: 0.5 * dimensions.y, z: 0 })); + Overlays.editOverlay(miniOverlay, { + localPosition: localPosition, + localRotation: localRotation, + dimensions: dimensions + }); + Overlays.editOverlay(miniUIOverlay, { + localPosition: Vec3.multiply(tabletScaleFactor, MINI_UI_LOCAL_POSITION), + dimensions: Vec3.multiply(tabletScaleFactor, MINI_UI_DIMENSIONS), + dpi: MINI_UI_DPI / tabletScaleFactor + }); + } + + function hide() { + Overlays.editOverlay(miniOverlay, { + parentID: Uuid.NULL, // Release hold so that hand can grab tablet proper. + visible: false + }); + Overlays.editOverlay(miniUIOverlay, { + visible: false + }); + } + + function create() { + miniOverlay = Overlays.addOverlay("model", { + url: MINI_MODEL, + dimensions: Vec3.multiply(avatarScale, MINI_DIMENSIONS), + solid: true, + grabbable: true, + showKeyboardFocusHighlight: false, + displayInFront: true, + visible: false + }); + miniUIOverlay = Overlays.addOverlay("web3d", { + url: MINI_UI_HTML, + parentID: miniOverlay, + localPosition: Vec3.multiply(avatarScale, MINI_UI_LOCAL_POSITION), + localRotation: MINI_UI_LOCAL_ROTATION, + dimensions: Vec3.multiply(avatarScale, MINI_UI_DIMENSIONS), + dpi: MINI_UI_DPI / avatarScale, + alpha: 0, // Hide overlay while its content is being created. + grabbable: false, + showKeyboardFocusHighlight: false, + displayInFront: true, + visible: false + }); + + miniUIOverlayEnabled = false; // This and alpha = 0 hides overlay while its content is being created. + + miniOverlayObject = Overlays.getOverlayObject(miniUIOverlay); + miniOverlayObject.webEventReceived.connect(onWebEventReceived); + + // updateMiniTabletID(); Other scripts relying on this may not be ready yet so do this in showUI(). + } + + function destroy() { + if (miniOverlayObject) { + miniOverlayObject.webEventReceived.disconnect(onWebEventReceived); + Overlays.deleteOverlay(miniUIOverlay); + Overlays.deleteOverlay(miniOverlay); + miniOverlayObject = null; + miniUIOverlay = null; + miniOverlay = null; + updateMiniTabletID(); + } + } + + create(); + + return { + getUIPositionAndRotation: getUIPositionAndRotation, + getMiniTabletID: getMiniTabletID, + getMiniTabletProperties: getMiniTabletProperties, + updateMutedStatus: updateMutedStatus, + updateBubbleStatus: updateBubbleStatus, + show: show, + size: size, + startExpandingTablet: startExpandingTablet, + sizeAboutHandles: sizeAboutHandles, + hide: hide, + destroy: destroy + }; + + }; // #endregion // #region State Machine =================================================================================================== - function enterProxyDisabled() { - // Stop updates. - if (updateTimer !== null) { - Script.clearTimeout(updateTimer); - updateTimer = null; + State = function () { + + if (!(this instanceof State)) { + return new State(); } - // Stop monitoring mute and bubble changes. - Audio.mutedChanged.disconnect(updateMutedStatus); - Users.ignoreRadiusEnabledChanged.disconnect(updateBubbleStatus); + var + // States. + MINI_DISABLED = 0, + MINI_HIDDEN = 1, + MINI_HIDING = 2, + MINI_SHOWING = 3, + MINI_VISIBLE = 4, + MINI_GRABBED = 5, + MINI_EXPANDING = 6, + TABLET_OPEN = 7, + STATE_STRINGS = ["MINI_DISABLED", "MINI_HIDDEN", "MINI_HIDING", "MINI_SHOWING", "MINI_VISIBLE", "MINI_GRABBED", + "MINI_EXPANDING", "TABLET_OPEN"], + STATE_MACHINE, + miniState = MINI_DISABLED, + miniHand, + updateTimer = null, + UPDATE_INTERVAL = 300, - // Don't keep overlays prepared if in desktop mode. - destroyUI(); - } + // Mini tablet scaling. + MINI_SCALE_DURATION = 150, + MINI_SCALE_TIMEOUT = 20, + miniScaleTimer = null, + miniScaleStart, - function exitProxyDisabled() { - // Create UI so that it's ready to be displayed without seeing artefacts from creating the UI. - createUI(); + // Expansion to tablet. + MINI_EXPAND_DURATION = 250, + MINI_EXPAND_TIMEOUT = 20, + miniExpandTimer = null, + miniExpandStart, - // Start monitoring mute and bubble changes. - Audio.mutedChanged.connect(updateMutedStatus); - Users.ignoreRadiusEnabledChanged.connect(updateBubbleStatus); + // Visibility. + MIN_HAND_CAMERA_ANGLE = 30, + DEGREES_180 = 180, + MIN_HAND_CAMERA_ANGLE_COS = Math.cos(Math.PI * MIN_HAND_CAMERA_ANGLE / DEGREES_180); - // Start updates. - updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL); - } - function shouldShowProxy(hand) { - // Should show proxy if it would be oriented toward the camera. - var pose, - jointIndex, - handPosition, - handOrientation, - proxyPosition, - proxyOrientation, - proxyToCameraDirection; + function enterMiniDisabled() { + // Stop updates. + if (updateTimer !== null) { + Script.clearTimeout(updateTimer); + updateTimer = null; + } - pose = Controller.getPoseValue(hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand); - if (!pose.valid) { - return false; + // Stop monitoring mute and bubble changes. + Audio.mutedChanged.disconnect(ui.updateMutedStatus); + Users.ignoreRadiusEnabledChanged.disconnect(ui.updateBubbleStatus); + + // Don't keep overlays prepared if in desktop mode. + ui.destroy(); + ui = null; } - jointIndex = handJointIndex(hand); - handPosition = Vec3.sum(MyAvatar.position, - Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex))); - handOrientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex)); - proxyPosition = Vec3.sum(handPosition, Vec3.multiply(avatarScale, - Vec3.multiplyQbyV(handOrientation, PROXY_POSITIONS[hand]))); - proxyOrientation = Quat.multiply(handOrientation, PROXY_ROTATIONS[hand]); - proxyToCameraDirection = Vec3.normalize(Vec3.subtract(Camera.position, proxyPosition)); - return Vec3.dot(proxyToCameraDirection, Quat.getForward(proxyOrientation)) > MIN_HAND_CAMERA_ANGLE_COS; - } + function exitMiniDisabled() { + // Create UI so that it's ready to be displayed without seeing artefacts from creating the UI. + ui = new UI(); - function enterProxyHidden() { - hideUI(); - } + // Start monitoring mute and bubble changes. + Audio.mutedChanged.connect(ui.updateMutedStatus); + Users.ignoreRadiusEnabledChanged.connect(ui.updateBubbleStatus); - function updateProxyHidden() { - // Don't show proxy if tablet is already displayed or in toolbar mode. - if (HMD.showTablet || tablet.toolbarMode) { - return; - } - // Compare palm directions of hands with vectors from palms to camera. - if (shouldShowProxy(LEFT_HAND)) { - setState(PROXY_SHOWING, LEFT_HAND); - } else if (shouldShowProxy(RIGHT_HAND)) { - setState(PROXY_SHOWING, RIGHT_HAND); - } - } - - function scaleProxyDown() { - var scaleFactor = (Date.now() - proxyScaleStart) / PROXY_SCALE_DURATION; - if (scaleFactor < 1) { - sizeUI((1 - scaleFactor) * avatarScale); - proxyScaleTimer = Script.setTimeout(scaleProxyDown, PROXY_SCALE_TIMEOUT); - return; - } - proxyScaleTimer = null; - setState(PROXY_HIDDEN); - } - - function enterProxyHiding() { - proxyScaleStart = Date.now(); - proxyScaleTimer = Script.setTimeout(scaleProxyDown, PROXY_SCALE_TIMEOUT); - } - - function updateProxyHiding() { - if (HMD.showTablet) { - setState(PROXY_HIDDEN); - } - } - - function exitProxyHiding() { - if (proxyScaleTimer) { - Script.clearTimeout(proxyScaleTimer); - proxyScaleTimer = null; - } - } - - function scaleProxyUp() { - var scaleFactor = (Date.now() - proxyScaleStart) / PROXY_SCALE_DURATION; - if (scaleFactor < 1) { - sizeUI(scaleFactor * avatarScale); - proxyScaleTimer = Script.setTimeout(scaleProxyUp, PROXY_SCALE_TIMEOUT); - return; - } - proxyScaleTimer = null; - sizeUI(avatarScale); - setState(PROXY_VISIBLE); - } - - function enterProxyShowing(hand) { - proxyHand = hand; - showUI(); - proxyScaleStart = Date.now(); - proxyScaleTimer = Script.setTimeout(scaleProxyUp, PROXY_SCALE_TIMEOUT); - } - - function updateProxyShowing() { - if (HMD.showTablet) { - setState(PROXY_HIDDEN); - } - } - - function exitProxyShowing() { - if (proxyScaleTimer) { - Script.clearTimeout(proxyScaleTimer); - proxyScaleTimer = null; - } - } - - function updateProxyVisible() { - // Hide proxy if tablet has been displayed by other means. - if (HMD.showTablet) { - setState(PROXY_HIDDEN); - return; - } - // Check that palm direction of proxy hand still less than maximum angle. - if (!shouldShowProxy(proxyHand)) { - setState(PROXY_HIDING); - } - } - - function updateProxyGrabbed() { - // Hide proxy if tablet has been displayed by other means. - if (HMD.showTablet) { - setState(PROXY_HIDDEN); - } - } - - function expandProxy() { - var scaleFactor = (Date.now() - proxyExpandStart) / PROXY_EXPAND_DURATION; - if (scaleFactor < 1) { - sizeUIAboutHandles(scaleFactor); - proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); - return; - } - proxyExpandTimer = null; - setState(TABLET_OPEN); - } - - function enterProxyExpanding(hand) { - // Expansion details. - if (hand === proxyHand) { - proxyExpandHandles = PROXY_EXPAND_HANDLES; - proxyExpandDeltaRotation = PROXY_EXPAND_DELTA_ROTATION; - } else { - proxyExpandHandles = PROXY_EXPAND_HANDLES_OTHER; - proxyExpandDeltaRotation = PROXY_EXPAND_DELTA_ROTATION_OTHER; + // Start updates. + updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL); } - // Grab details. - var properties = Overlays.getProperties(proxyOverlay, ["localPosition", "localRotation"]); - proxyExpandHand = hand; - proxyExpandLocalRotation = properties.localRotation; - proxyExpandLocalPosition = Vec3.sum(properties.localPosition, - Vec3.multiplyQbyV(proxyExpandLocalRotation, - Vec3.multiplyVbyV(proxyExpandHandles[proxyExpandHand], PROXY_DIMENSIONS))); + function shouldShowMini(hand) { + // Should show mini tablet if it would be oriented toward the camera. + var pose, + jointIndex, + handPosition, + handOrientation, + uiPositionAndOrientation, + miniPosition, + miniOrientation, + miniToCameraDirection; - // Start expanding. - proxyInitialWidth = PROXY_DIMENSIONS.x; - proxyTargetWidth = getTabletWidthFromSettings(); - proxyTargetLocalRotation = Quat.multiply(proxyExpandLocalRotation, proxyExpandDeltaRotation); - proxyExpandStart = Date.now(); - proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT); - } + pose = Controller.getPoseValue(hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand); + if (!pose.valid) { + return false; + } - function updateProxyExanding() { - // Hide proxy immediately if tablet has been displayed by other means. - if (HMD.showTablet) { - setState(PROXY_HIDDEN); + jointIndex = handJointIndex(hand); + handPosition = Vec3.sum(MyAvatar.position, + Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex))); + handOrientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex)); + uiPositionAndOrientation = ui.getUIPositionAndRotation(hand); + miniPosition = Vec3.sum(handPosition, Vec3.multiply(avatarScale, + Vec3.multiplyQbyV(handOrientation, uiPositionAndOrientation.position))); + miniOrientation = Quat.multiply(handOrientation, uiPositionAndOrientation.rotation); + miniToCameraDirection = Vec3.normalize(Vec3.subtract(Camera.position, miniPosition)); + return Vec3.dot(miniToCameraDirection, Quat.getForward(miniOrientation)) > MIN_HAND_CAMERA_ANGLE_COS; } - } - function exitProxyExpanding() { - if (proxyExpandTimer !== null) { - Script.clearTimeout(proxyExpandTimer); - proxyExpandTimer = null; + function enterMiniHidden() { + ui.hide(); } - } - function enterTabletOpen() { - var proxyOverlayProperties = Overlays.getProperties(proxyOverlay, ["position", "orientation"]); - - hideUI(); - - Overlays.editOverlay(HMD.tabletID, { - position: proxyOverlayProperties.position, - orientation: proxyOverlayProperties.orientation - }); - HMD.openTablet(true); - } - - function updateTabletOpen() { - // Immediately transition back to PROXY_HIDDEN. - setState(PROXY_HIDDEN); - } - - STATE_MACHINE = { - PROXY_DISABLED: { // Tablet proxy cannot be shown because in desktop mode. - enter: enterProxyDisabled, - update: null, - exit: exitProxyDisabled - }, - PROXY_HIDDEN: { // Tablet proxy could be shown but isn't because hand is oriented to show it or aren't in HMD mode. - enter: enterProxyHidden, - update: updateProxyHidden, - exit: null - }, - PROXY_HIDING: { // Tablet proxy is reducing from PROXY_VISIBLE to PROXY_HIDDEN. - enter: enterProxyHiding, - update: updateProxyHiding, - exit: exitProxyHiding - }, - PROXY_SHOWING: { // Tablet proxy is expanding from PROXY_HIDDN to PROXY_VISIBLE. - enter: enterProxyShowing, - update: updateProxyShowing, - exit: exitProxyShowing - }, - PROXY_VISIBLE: { // Tablet proxy is visible and attached to hand. - enter: null, - update: updateProxyVisible, - exit: null - }, - PROXY_GRABBED: { // Tablet proxy is grabbed by other hand. - enter: null, - update: updateProxyGrabbed, - exit: null - }, - PROXY_EXPANDING: { // Tablet proxy is expanding before showing tablet proper. - enter: enterProxyExpanding, - update: updateProxyExanding, - exit: exitProxyExpanding - }, - TABLET_OPEN: { // Tablet proper is being displayed. - enter: enterTabletOpen, - update: updateTabletOpen, - exit: null + function updateMiniHidden() { + // Don't show mini tablet if tablet proper is already displayed or in toolbar mode. + if (HMD.showTablet || tablet.toolbarMode) { + return; + } + // Compare palm directions of hands with vectors from palms to camera. + if (shouldShowMini(LEFT_HAND)) { + setState(MINI_SHOWING, LEFT_HAND); + } else if (shouldShowMini(RIGHT_HAND)) { + setState(MINI_SHOWING, RIGHT_HAND); + } } + + function scaleMiniDown() { + var scaleFactor = (Date.now() - miniScaleStart) / MINI_SCALE_DURATION; + if (scaleFactor < 1) { + ui.size((1 - scaleFactor) * avatarScale); + miniScaleTimer = Script.setTimeout(scaleMiniDown, MINI_SCALE_TIMEOUT); + return; + } + miniScaleTimer = null; + setState(MINI_HIDDEN); + } + + function enterMiniHiding() { + miniScaleStart = Date.now(); + miniScaleTimer = Script.setTimeout(scaleMiniDown, MINI_SCALE_TIMEOUT); + } + + function updateMiniHiding() { + if (HMD.showTablet) { + setState(MINI_HIDDEN); + } + } + + function exitMiniHiding() { + if (miniScaleTimer) { + Script.clearTimeout(miniScaleTimer); + miniScaleTimer = null; + } + } + + function scaleMiniUp() { + var scaleFactor = (Date.now() - miniScaleStart) / MINI_SCALE_DURATION; + if (scaleFactor < 1) { + ui.size(scaleFactor * avatarScale); + miniScaleTimer = Script.setTimeout(scaleMiniUp, MINI_SCALE_TIMEOUT); + return; + } + miniScaleTimer = null; + ui.size(avatarScale); + setState(MINI_VISIBLE); + } + + function enterMiniShowing(hand) { + miniHand = hand; + ui.show(miniHand); + miniScaleStart = Date.now(); + miniScaleTimer = Script.setTimeout(scaleMiniUp, MINI_SCALE_TIMEOUT); + } + + function updateMiniShowing() { + if (HMD.showTablet) { + setState(MINI_HIDDEN); + } + } + + function exitMiniShowing() { + if (miniScaleTimer) { + Script.clearTimeout(miniScaleTimer); + miniScaleTimer = null; + } + } + + function updateMiniVisible() { + // Hide mini tablet if tablet proper has been displayed by other means. + if (HMD.showTablet) { + setState(MINI_HIDDEN); + return; + } + // Check that palm direction of mini tablet hand still less than maximum angle. + if (!shouldShowMini(miniHand)) { + setState(MINI_HIDING); + } + } + + function updateMiniGrabbed() { + // Hide mini tablet if tablet proper has been displayed by other means. + if (HMD.showTablet) { + setState(MINI_HIDDEN); + } + } + + function expandMini() { + var scaleFactor = (Date.now() - miniExpandStart) / MINI_EXPAND_DURATION; + if (scaleFactor < 1) { + ui.sizeAboutHandles(scaleFactor); + miniExpandTimer = Script.setTimeout(expandMini, MINI_EXPAND_TIMEOUT); + return; + } + miniExpandTimer = null; + setState(TABLET_OPEN); + } + + function enterMiniExpanding(hand) { + ui.startExpandingTablet(hand); + miniExpandStart = Date.now(); + miniExpandTimer = Script.setTimeout(expandMini, MINI_EXPAND_TIMEOUT); + } + + function updateMiniExanding() { + // Hide mini tablet immediately if tablet proper has been displayed by other means. + if (HMD.showTablet) { + setState(MINI_HIDDEN); + } + } + + function exitMiniExpanding() { + if (miniExpandTimer !== null) { + Script.clearTimeout(miniExpandTimer); + miniExpandTimer = null; + } + } + + function enterTabletOpen() { + var miniTabletProperties = ui.getMiniTabletProperties(); + + ui.hide(); + + Overlays.editOverlay(HMD.tabletID, { + position: miniTabletProperties.position, + orientation: miniTabletProperties.orientation + }); + HMD.openTablet(true); + } + + function updateTabletOpen() { + // Immediately transition back to MINI_HIDDEN. + setState(MINI_HIDDEN); + } + + STATE_MACHINE = { + MINI_DISABLED: { // Mini tablet cannot be shown because in desktop mode. + enter: enterMiniDisabled, + update: null, + exit: exitMiniDisabled + }, + MINI_HIDDEN: { // Mini tablet could be shown but isn't because hand is oriented to show it or aren't in HMD mode. + enter: enterMiniHidden, + update: updateMiniHidden, + exit: null + }, + MINI_HIDING: { // Mini tablet is reducing from MINI_VISIBLE to MINI_HIDDEN. + enter: enterMiniHiding, + update: updateMiniHiding, + exit: exitMiniHiding + }, + MINI_SHOWING: { // Mini tablet is expanding from MINI_HIDDN to MINI_VISIBLE. + enter: enterMiniShowing, + update: updateMiniShowing, + exit: exitMiniShowing + }, + MINI_VISIBLE: { // Mini tablet is visible and attached to hand. + enter: null, + update: updateMiniVisible, + exit: null + }, + MINI_GRABBED: { // Mini tablet is grabbed by other hand. + enter: null, + update: updateMiniGrabbed, + exit: null + }, + MINI_EXPANDING: { // Mini tablet is expanding before showing tablet proper. + enter: enterMiniExpanding, + update: updateMiniExanding, + exit: exitMiniExpanding + }, + TABLET_OPEN: { // Tablet proper is being displayed. + enter: enterTabletOpen, + update: updateTabletOpen, + exit: null + } + }; + + function setState(state, data) { + if (state !== miniState) { + debug("State transition from " + STATE_STRINGS[miniState] + " to " + STATE_STRINGS[state]); + if (STATE_MACHINE[STATE_STRINGS[miniState]].exit) { + STATE_MACHINE[STATE_STRINGS[miniState]].exit(data); + } + if (STATE_MACHINE[STATE_STRINGS[state]].enter) { + STATE_MACHINE[STATE_STRINGS[state]].enter(data); + } + miniState = state; + } else { + error("Null state transition: " + state + "!"); + } + } + + function getState() { + return miniState; + } + + function getHand() { + return miniHand; + } + + function updateState() { + if (STATE_MACHINE[STATE_STRINGS[miniState]].update) { + STATE_MACHINE[STATE_STRINGS[miniState]].update(); + } + updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL); + } + + function create() { + // Nothing to do. + } + + function destroy() { + if (miniState !== MINI_DISABLED) { + setState(MINI_DISABLED); + } + } + + create(); + + return { + MINI_DISABLED: MINI_DISABLED, + MINI_HIDDEN: MINI_HIDDEN, + MINI_HIDING: MINI_HIDING, + MINI_SHOWING: MINI_SHOWING, + MINI_VISIBLE: MINI_VISIBLE, + MINI_GRABBED: MINI_GRABBED, + MINI_EXPANDING: MINI_EXPANDING, + TABLET_OPEN: TABLET_OPEN, + updateState: updateState, + setState: setState, + getState: getState, + getHand: getHand, + destroy: destroy + }; }; - function setState(state, data) { - if (state !== rezzerState) { - debug("State transition from " + STATE_STRINGS[rezzerState] + " to " + STATE_STRINGS[state]); - if (STATE_MACHINE[STATE_STRINGS[rezzerState]].exit) { - STATE_MACHINE[STATE_STRINGS[rezzerState]].exit(data); - } - if (STATE_MACHINE[STATE_STRINGS[state]].enter) { - STATE_MACHINE[STATE_STRINGS[state]].enter(data); - } - rezzerState = state; - } else { - error("Null state transition: " + state + "!"); - } - } - - function updateState() { - if (STATE_MACHINE[STATE_STRINGS[rezzerState]].update) { - STATE_MACHINE[STATE_STRINGS[rezzerState]].update(); - } - updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL); - } - // #endregion - // #region Events ========================================================================================================== + // #region External Events ================================================================================================= function onScaleChanged() { avatarScale = MyAvatar.scale; @@ -673,44 +790,49 @@ } function onMessageReceived(channel, data, senderID, localOnly) { - var message, hand; + var message, + miniHand, + hand; if (channel !== HIFI_OBJECT_MANIPULATION_CHANNEL) { return; } message = JSON.parse(data); - if (message.grabbedEntity !== proxyOverlay) { + if (message.grabbedEntity !== ui.getMiniTabletID()) { return; } - if (message.action === "grab" && rezzerState === PROXY_VISIBLE) { - hand = message.joint === HAND_NAMES[proxyHand] ? proxyHand : otherHand(proxyHand); - if (hand === proxyHand) { - setState(PROXY_EXPANDING, hand); + miniHand = miniState.getHand(); + if (message.action === "grab" && miniState.getState() === miniState.MINI_VISIBLE) { + hand = message.joint === HAND_NAMES[miniHand] ? miniHand : otherHand(miniHand); + if (hand === miniHand) { + miniState.setState(miniState.MINI_EXPANDING, hand); } else { - setState(PROXY_GRABBED); + miniState.setState(miniState.MINI_GRABBED); } - } else if (message.action === "release" && rezzerState === PROXY_GRABBED) { - hand = message.joint === HAND_NAMES[proxyHand] ? proxyHand : otherHand(proxyHand); - setState(PROXY_EXPANDING, hand); + } else if (message.action === "release" && miniState.getState() === miniState.MINI_GRABBED) { + hand = message.joint === HAND_NAMES[miniHand] ? miniHand : otherHand(miniHand); + miniState.setState(miniState.MINI_EXPANDING, hand); } } function onDisplayModeChanged() { - // Tablet proxy only available when HMD is active. + // Mini tablet only available when HMD is active. if (HMD.active) { - setState(PROXY_HIDDEN); + miniState.setState(miniState.MINI_HIDDEN); } else { - setState(PROXY_DISABLED); + miniState.setState(miniState.MINI_DISABLED); } } // #endregion - // #region Start-up and tear-down ========================================================================================== + // #region Set-up and tear-down ============================================================================================ function setUp() { + miniState = new State(); + MyAvatar.scaleChanged.connect(onScaleChanged); Messages.subscribe(HIFI_OBJECT_MANIPULATION_CHANNEL); @@ -718,17 +840,12 @@ HMD.displayModeChanged.connect(onDisplayModeChanged); if (HMD.active) { - setState(PROXY_HIDDEN); + miniState.setState(miniState.MINI_HIDDEN); } } function tearDown() { - if (updateTimer !== null) { - Script.clearTimeout(updateTimer); - updateTimer = null; - } - - setState(PROXY_DISABLED); + miniState.setState(miniState.MINI_DISABLED); HMD.displayModeChanged.disconnect(onDisplayModeChanged); @@ -736,6 +853,9 @@ Messages.unsubscribe(HIFI_OBJECT_MANIPULATION_CHANNEL); MyAvatar.scaleChanged.disconnect(onScaleChanged); + + miniState.destroy(); + miniState = null; } setUp(); From 2443723a2bfc324d69e5270e062e699a299c4177 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Thu, 6 Sep 2018 21:14:04 -0700 Subject: [PATCH 353/744] Removing margins, adding width attribute --- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index d2a97874b1..707c173382 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -97,6 +97,7 @@ Item { } text: qsTr("Sign in to High Fidelity to make friends, get HFC, and buy interesting things on the Marketplace!") + width: parent.width wrapMode: Text.WordWrap lineHeight: 1 lineHeightMode: Text.ProportionalHeight @@ -146,7 +147,6 @@ Item { anchors { right: usernameField.right top: usernameField.bottom - topMargin: -19 } text: "Forgot Username?" @@ -174,7 +174,6 @@ Item { anchors { right: passwordField.right top: passwordField.bottom - topMargin: -19 } text: "Forgot Password?" From f3cbca6cee95ebb24de5309ff73bc8a2d3687c5a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 7 Sep 2018 16:43:21 +1200 Subject: [PATCH 354/744] Change "bubble" button to "goto" --- .../system/html/css/img/mt-bubble-a-hover.svg | 5 -- .../html/css/img/mt-bubble-a-normal.svg | 5 -- ...t-bubble-i-hover.svg => mt-goto-hover.svg} | 0 ...bubble-i-normal.svg => mt-goto-normal.svg} | 0 scripts/system/html/css/miniTablet.css | 18 ++----- scripts/system/html/js/miniTablet.js | 24 ++++----- scripts/system/html/miniTablet.html | 4 +- scripts/system/miniTablet.js | 54 ++++++++++--------- 8 files changed, 48 insertions(+), 62 deletions(-) delete mode 100644 scripts/system/html/css/img/mt-bubble-a-hover.svg delete mode 100644 scripts/system/html/css/img/mt-bubble-a-normal.svg rename scripts/system/html/css/img/{mt-bubble-i-hover.svg => mt-goto-hover.svg} (100%) rename scripts/system/html/css/img/{mt-bubble-i-normal.svg => mt-goto-normal.svg} (100%) diff --git a/scripts/system/html/css/img/mt-bubble-a-hover.svg b/scripts/system/html/css/img/mt-bubble-a-hover.svg deleted file mode 100644 index 88ddfff808..0000000000 --- a/scripts/system/html/css/img/mt-bubble-a-hover.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/scripts/system/html/css/img/mt-bubble-a-normal.svg b/scripts/system/html/css/img/mt-bubble-a-normal.svg deleted file mode 100644 index 3d9d0a1286..0000000000 --- a/scripts/system/html/css/img/mt-bubble-a-normal.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/scripts/system/html/css/img/mt-bubble-i-hover.svg b/scripts/system/html/css/img/mt-goto-hover.svg similarity index 100% rename from scripts/system/html/css/img/mt-bubble-i-hover.svg rename to scripts/system/html/css/img/mt-goto-hover.svg diff --git a/scripts/system/html/css/img/mt-bubble-i-normal.svg b/scripts/system/html/css/img/mt-goto-normal.svg similarity index 100% rename from scripts/system/html/css/img/mt-bubble-i-normal.svg rename to scripts/system/html/css/img/mt-goto-normal.svg diff --git a/scripts/system/html/css/miniTablet.css b/scripts/system/html/css/miniTablet.css index 3b01a45613..d5a12aae9c 100644 --- a/scripts/system/html/css/miniTablet.css +++ b/scripts/system/html/css/miniTablet.css @@ -57,25 +57,17 @@ img { background-image: url("./img/mt-mute-hover.svg"); } -#bubble { +#goto { padding-top: 19px; background-size: 100% 100%; } - #bubble.off { - background-image: url("./img/mt-bubble-i-normal.svg"); + #goto.off { + background-image: url("./img/mt-goto-normal.svg"); } - #bubble.off:hover { - background-image: url("./img/mt-bubble-i-hover.svg"); - } - - #bubble.on { - background-image: url("./img/mt-bubble-a-normal.svg"); - } - - #bubble.on:hover { - background-image: url("./img/mt-bubble-a-hover.svg"); + #goto.off:hover { + background-image: url("./img/mt-goto-hover.svg"); } #expand { diff --git a/scripts/system/html/js/miniTablet.js b/scripts/system/html/js/miniTablet.js index ab1790cf00..f21dc7a6e6 100644 --- a/scripts/system/html/js/miniTablet.js +++ b/scripts/system/html/js/miniTablet.js @@ -19,13 +19,13 @@ READY_MESSAGE = "ready", // Engine <== Dialog HOVER_MESSAGE = "hover", // Engine <== Dialog MUTE_MESSAGE = "mute", // Engine <=> Dialog - BUBBLE_MESSAGE = "bubble", // Engine <=> Dialog + GOTO_MESSAGE = "goto", // Engine <=> Dialog EXPAND_MESSAGE = "expand", // Engine <== Dialog muteButton, muteImage, - bubbleButton, - bubbleImage, + gotoButton, + gotoImage, expandButton; // #region Communications ================================================================================================== @@ -44,10 +44,8 @@ case MUTE_MESSAGE: muteImage.src = message.icon; break; - case BUBBLE_MESSAGE: - bubbleButton.classList.remove(message.on ? "off" : "on"); - bubbleButton.classList.add(message.on ? "on" : "off"); - bubbleImage.src = message.icon; + case GOTO_MESSAGE: + gotoImage.src = message.icon; break; } } @@ -64,9 +62,9 @@ })); } - function onBubbleButtonClick() { + function onGotoButtonClick() { EventBridge.emitWebEvent(JSON.stringify({ - type: BUBBLE_MESSAGE + type: GOTO_MESSAGE })); } @@ -98,17 +96,17 @@ function onLoad() { muteButton = document.getElementById("mute"); muteImage = document.getElementById("mute-img"); - bubbleButton = document.getElementById("bubble"); - bubbleImage = document.getElementById("bubble-img"); + gotoButton = document.getElementById("goto"); + gotoImage = document.getElementById("goto-img"); expandButton = document.getElementById("expand"); connectEventBridge(); muteButton.addEventListener("mouseenter", onButtonHover, false); - bubbleButton.addEventListener("mouseenter", onButtonHover, false); + gotoButton.addEventListener("mouseenter", onButtonHover, false); expandButton.addEventListener("mouseenter", onButtonHover, false); muteButton.addEventListener("click", onMuteButtonClick, true); - bubbleButton.addEventListener("click", onBubbleButtonClick, true); + gotoButton.addEventListener("click", onGotoButtonClick, true); expandButton.addEventListener("click", onExpandButtonClick, true); document.body.onunload = function () { diff --git a/scripts/system/html/miniTablet.html b/scripts/system/html/miniTablet.html index 4b3129fca9..eca21c0d68 100644 --- a/scripts/system/html/miniTablet.html +++ b/scripts/system/html/miniTablet.html @@ -20,8 +20,8 @@ See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.